@convertcom/mcp-server 0.0.1-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +301 -0
- package/build/api-config.d.ts +1 -0
- package/build/api-config.js +40 -0
- package/build/api-config.js.map +1 -0
- package/build/index.d.ts +12 -0
- package/build/index.js +312 -0
- package/build/index.js.map +1 -0
- package/build/tools.d.ts +16 -0
- package/build/tools.js +1149 -0
- package/build/tools.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# Convert MCP Server
|
|
2
|
+
|
|
3
|
+
Connect your Convert optimization data directly to Claude Desktop, Cursor, or any Model Context Protocol-compatible AI assistant. No middleware, no complex setup—just plug and play.
|
|
4
|
+
|
|
5
|
+
**What is MCP?**
|
|
6
|
+
|
|
7
|
+
MCP (Model Context Protocol) is an emerging standard that allows AI models to interact with applications through a consistent interface. It acts as an abstraction layer, letting AI agents access your Convert data without needing to understand specific API protocols.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 🚀 Quick Start
|
|
12
|
+
|
|
13
|
+
### Prerequisites
|
|
14
|
+
|
|
15
|
+
**Get your Convert API credentials:**
|
|
16
|
+
|
|
17
|
+
- Log into your Convert account
|
|
18
|
+
- Go to Settings → Integrations → API
|
|
19
|
+
- Generate or copy your Application ID and Secret Key
|
|
20
|
+
- _Consider starting with read-only access (`TOOLS_FOR_CLIENT=reporting` or `TOOLS_FOR_CLIENT=readOnly` mod - see below)_
|
|
21
|
+
|
|
22
|
+
### Installation & Setup
|
|
23
|
+
|
|
24
|
+
#### Option 1: NPX (Recommended)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# No installation needed - run directly
|
|
28
|
+
npx @convertcom/mcp-server
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
#### Option 2: Global Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Install globally
|
|
35
|
+
npm install -g @convertcom/mcp-server
|
|
36
|
+
|
|
37
|
+
# Run the server
|
|
38
|
+
mcp-server-convert
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 🔧 Client Configuration
|
|
44
|
+
|
|
45
|
+
### Claude Desktop
|
|
46
|
+
|
|
47
|
+
1. **Download Claude Desktop** from [claude.ai](https://claude.ai/download)
|
|
48
|
+
|
|
49
|
+
2. **Add server configuration** to your Claude Desktop config file:
|
|
50
|
+
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
51
|
+
- **Windows**: `%APPDATA%/Claude/claude_desktop_config.json`
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"convert": {
|
|
57
|
+
"command": "npx",
|
|
58
|
+
"args": ["-y", "@convertcom/mcp-server"],
|
|
59
|
+
"env": {
|
|
60
|
+
"CONVERT_API_KEY": "your_convert_application_id",
|
|
61
|
+
"CONVERT_API_SECRET": "your_convert_secret_key",
|
|
62
|
+
"TOOLS_FOR_CLIENT": "reporting"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
1. **Save the file and restart Claude Desktop**. You should now see Convert tools available!
|
|
70
|
+
|
|
71
|
+
2. **Visit the [troubleshooting section](#-troubleshooting)** if you encounter any issues.
|
|
72
|
+
|
|
73
|
+
### Cursor
|
|
74
|
+
|
|
75
|
+
1. **Create a `.cursor/mcp.json` file** in your project:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
mkdir -p .cursor && touch .cursor/mcp.json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
2. **Add the following configuration**:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"mcpServers": {
|
|
86
|
+
"convert": {
|
|
87
|
+
"command": "npx",
|
|
88
|
+
"args": ["-y", "@convertcom/mcp-server"],
|
|
89
|
+
"env": {
|
|
90
|
+
"CONVERT_API_KEY": "your_convert_application_id",
|
|
91
|
+
"CONVERT_API_SECRET": "your_convert_secret_key",
|
|
92
|
+
"TOOLS_FOR_CLIENT": "reporting"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
3. **Save the file and restart Cursor**. Convert tools will now be available!
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 💬 Example Usage
|
|
104
|
+
|
|
105
|
+
Once set up, try these example prompts with your AI assistant:
|
|
106
|
+
|
|
107
|
+
### Get Insights from Your Convert Data
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
Show me the performance of all active experiments in my account
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
How many experiences do I have running right now?
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
Summarize all A/B tests with conversion rates above 5% from the last 30 days
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Analyze Experiment Performance
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
Get me the latest conversion data for my homepage experiment
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
Compare the performance of mobile vs desktop experiments this month
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
Show me which audience segments are performing best across all active tests
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Create and Manage Tests (Full Access Mode)
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
Create a new A/B test for the pricing page targeting returning visitors with a 70/30 traffic split
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
Pause any experiences that have been running for more than 30 days with negative lift
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
Update the traffic allocation for experiment ID 12345 to 50/50
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## ⚙️ Configuration
|
|
152
|
+
|
|
153
|
+
All configuration is handled through environment variables for maximum compatibility across different MCP clients.
|
|
154
|
+
|
|
155
|
+
| Variable | Required | Description | Default |
|
|
156
|
+
| -------------------- | -------- | ----------------------------------------------- | ----------- |
|
|
157
|
+
| `CONVERT_API_KEY` | ✅ | Your Convert **Application ID** | - |
|
|
158
|
+
| `CONVERT_API_SECRET` | ✅ | Your Convert **Secret Key** | - |
|
|
159
|
+
| `TOOLS_FOR_CLIENT` | | Access level: `reporting`, `readOnly`, or `all` | `reporting` |
|
|
160
|
+
|
|
161
|
+
### Access Levels
|
|
162
|
+
|
|
163
|
+
Choose the appropriate access level based on your needs:
|
|
164
|
+
|
|
165
|
+
#### 🔍 `reporting` (Default - Recommended)
|
|
166
|
+
|
|
167
|
+
**Perfect for analytics and insights**
|
|
168
|
+
|
|
169
|
+
- Account data and project information
|
|
170
|
+
- Real-time experiment tracking and performance reports
|
|
171
|
+
- Goals, audiences, and conversion analytics
|
|
172
|
+
- Live visitor data and traffic allocation
|
|
173
|
+
|
|
174
|
+
#### 📖 `readOnly`
|
|
175
|
+
|
|
176
|
+
**All reporting tools plus detailed configuration access**
|
|
177
|
+
|
|
178
|
+
- Everything in `reporting` mode
|
|
179
|
+
- Detailed project and experience configurations
|
|
180
|
+
- Historical data and change logs
|
|
181
|
+
- Advanced analytics and forecasting data
|
|
182
|
+
|
|
183
|
+
#### 🔧 `all` (Use with caution)
|
|
184
|
+
|
|
185
|
+
**Complete access including write operations**
|
|
186
|
+
|
|
187
|
+
- Everything in `readOnly` mode
|
|
188
|
+
- Create, edit, and delete experiments
|
|
189
|
+
- Manage audiences, goals, and project settings
|
|
190
|
+
- Full account administration capabilities
|
|
191
|
+
|
|
192
|
+
> **⚠️ Security Note**: Start with `reporting` mode and only escalate permissions as needed. The `all` access level can modify your Convert account.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## 🛠️ Available Tools
|
|
197
|
+
|
|
198
|
+
The server provides **100+ tools** covering the complete Convert REST API v2. Here's a quick overview:
|
|
199
|
+
|
|
200
|
+
### Core Analytics Tools (All Access Levels)
|
|
201
|
+
|
|
202
|
+
| Category | Tools | Description |
|
|
203
|
+
| ----------------------- | ---------------------------------------------------------------------- | ---------------------------------------- |
|
|
204
|
+
| **Account Management** | `getaccountslist`, `getaccountlivedata` | Account overview and real-time tracking |
|
|
205
|
+
| **Project Operations** | `getprojectslist`, `getproject`, `getprojectlivedata` | Project configuration and live data |
|
|
206
|
+
| **Experience Tracking** | `getexperienceslist`, `getexperience`, `getexperienceaggregatedreport` | A/B test performance and analytics |
|
|
207
|
+
| **Goal Analytics** | `getgoal`, `getgoalbykey` | Conversion goal tracking and performance |
|
|
208
|
+
| **Audience Insights** | `getaudienceslist`, `getaudience` | Audience segment analysis |
|
|
209
|
+
| **Location Targeting** | `getlocationslist`, `getlocation` | URL targeting and location rules |
|
|
210
|
+
|
|
211
|
+
### Advanced Tools (ReadOnly & All Access)
|
|
212
|
+
|
|
213
|
+
| Category | Tools | Description |
|
|
214
|
+
| ------------------- | ---------------------------------------------------------------- | -------------------------------- |
|
|
215
|
+
| **Historical Data** | `getaccounthistory`, `getprojecthistory`, `getexperiencehistory` | Change logs and audit trails |
|
|
216
|
+
| **Screenshots** | `getexperiencevariationscreenshot` | Visual variation previews |
|
|
217
|
+
| **Knowledge Base** | `getknowledgebaseslist`, `getknowledgebase` | Test insights and learnings |
|
|
218
|
+
| **Observations** | `getobservationslist` | Recorded insights and hypotheses |
|
|
219
|
+
|
|
220
|
+
### Management Tools (All Access Only)
|
|
221
|
+
|
|
222
|
+
| Category | Tools | Description |
|
|
223
|
+
| -------------------------- | ---------------------------------------------------------- | ------------------------------ |
|
|
224
|
+
| **Experience Management** | `createexperience`, `updateexperience`, `deleteexperience` | Full A/B test lifecycle |
|
|
225
|
+
| **Goal Management** | `creategoal`, `updategoal`, `deletegoal`, `clonegoal` | Conversion goal administration |
|
|
226
|
+
| **Audience Management** | `createaudience`, `updateaudience`, `deleteaudience` | Audience segment management |
|
|
227
|
+
| **Project Administration** | `createproject`, `updateproject` | Project-level configuration |
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## 🔒 Security & Authentication
|
|
232
|
+
|
|
233
|
+
### API Security
|
|
234
|
+
|
|
235
|
+
- **HMAC-SHA256 signing**: Every request is cryptographically signed
|
|
236
|
+
- **Automatic expiration**: Requests expire after 60 seconds
|
|
237
|
+
- **No credential exposure**: API keys never leave your local environment
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 🔧 Troubleshooting
|
|
242
|
+
|
|
243
|
+
### Common Issues
|
|
244
|
+
|
|
245
|
+
| Symptom | Likely Cause | Solution |
|
|
246
|
+
| ---------------------------- | ----------------------------------- | ----------------------------------------------- |
|
|
247
|
+
| `401 Unauthorized` responses | Wrong API credentials or clock skew | Verify credentials, sync system clock |
|
|
248
|
+
| "Server not found" in Claude | JSON config syntax error | Check `command` and `args` fields |
|
|
249
|
+
| Missing expected tools | `TOOLS_FOR_CLIENT` too restrictive | Switch to `readOnly` or `all` and restart |
|
|
250
|
+
| Connection timeouts | Network/firewall issues | Check connectivity to Convert's API endpoints |
|
|
251
|
+
| "No data returned" | Empty account or wrong permissions | Verify account has data and API key permissions |
|
|
252
|
+
|
|
253
|
+
### Getting Help
|
|
254
|
+
|
|
255
|
+
1. **Check the logs**: Development mode provides detailed error information
|
|
256
|
+
2. **Verify credentials**: Ensure your API key and secret are correct and have proper permissions
|
|
257
|
+
3. **Test connectivity**: Confirm you can reach Convert's API endpoints
|
|
258
|
+
4. **Review permissions**: Make sure your Convert account has access to the data you're requesting
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## 📊 What You Can Do
|
|
263
|
+
|
|
264
|
+
### Real-time Analytics
|
|
265
|
+
|
|
266
|
+
- Monitor live experiment performance
|
|
267
|
+
- Track visitor behavior and conversions
|
|
268
|
+
- Analyze traffic allocation and statistical significance
|
|
269
|
+
- Get instant insights on goal performance
|
|
270
|
+
|
|
271
|
+
### Experiment Management
|
|
272
|
+
|
|
273
|
+
- Create and configure A/B tests
|
|
274
|
+
- Set up multivariate experiments
|
|
275
|
+
- Manage audience targeting and segmentation
|
|
276
|
+
- Control traffic allocation and experiment lifecycle
|
|
277
|
+
|
|
278
|
+
### Data-Driven Insights
|
|
279
|
+
|
|
280
|
+
- Historical performance analysis
|
|
281
|
+
- Conversion funnel optimization
|
|
282
|
+
- Audience behavior patterns
|
|
283
|
+
- ROI and revenue impact assessment
|
|
284
|
+
|
|
285
|
+
### AI-Powered Optimization
|
|
286
|
+
|
|
287
|
+
- Let AI analyze your experiment data
|
|
288
|
+
- Get recommendations for test improvements
|
|
289
|
+
- Automate routine optimization tasks
|
|
290
|
+
- Generate insights from complex data patterns
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## 🆘 Support
|
|
295
|
+
|
|
296
|
+
- **Documentation**: [Convert API Documentation](https://api.convert.com/doc/)
|
|
297
|
+
- **Convert Support**: [Convert Help Center](https://support.convert.com/)
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
_© 2025 Convert Insights, Inc. • Built with ❤️ for the optimization community_
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
axios.defaults.headers.post['Content-Type'] = 'application/json';
|
|
4
|
+
axios.defaults.headers.put['Content-Type'] = 'application/json';
|
|
5
|
+
//axios.defaults.headers.get['Content-Type'] = 'application/json';
|
|
6
|
+
axios.defaults.transformRequest = [
|
|
7
|
+
function (data, headers) {
|
|
8
|
+
//console.warn('data', data);
|
|
9
|
+
if (!data) {
|
|
10
|
+
data = {};
|
|
11
|
+
}
|
|
12
|
+
return JSON.stringify(data);
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
// Add a request interceptor to set the 'x-convert-nonce' header
|
|
16
|
+
axios.interceptors.request.use((config) => {
|
|
17
|
+
let applicationID = process.env.CONVERT_API_KEY;
|
|
18
|
+
let applicationSecretKey = process.env.CONVERT_API_SECRET;
|
|
19
|
+
let expires = 60; // In seconds. Request lifetime. If your requests take much time, try to increase this value
|
|
20
|
+
let requestURL = config.url;
|
|
21
|
+
// It's already stringified in raw body in Postman but make sure it stringified correctly without whitespaces etc
|
|
22
|
+
let requestBody = config.data ? JSON.stringify(config.data) : '{}';
|
|
23
|
+
//let expiresTimestamp = '1747916037.342'; //Date.now() / 1000 + expires;
|
|
24
|
+
let expiresTimestamp = Date.now() / 1000 + expires;
|
|
25
|
+
let signString = applicationID + '\n' + expiresTimestamp + '\n' + requestURL + '\n' + requestBody;
|
|
26
|
+
let hash = crypto.createHmac('sha256', applicationSecretKey).update(signString).digest('hex');
|
|
27
|
+
let authorization = 'Convert-HMAC-SHA256 Signature=' + hash;
|
|
28
|
+
// console.warn('Debug data:', data); // Or console.warn
|
|
29
|
+
// console.warn(`Debug RequestBody: ${requestBody}`);
|
|
30
|
+
// console.warn(`Debug SigningString: ${signString}`);
|
|
31
|
+
// console.warn(`Debug Api Secret: ${applicationSecretKey ? 'SET' : 'NOT SET'}`); // Avoid logging secrets
|
|
32
|
+
config.headers['Convert-Application-ID'] = applicationID;
|
|
33
|
+
config.headers['Expires'] = expiresTimestamp;
|
|
34
|
+
config.headers['Authorization'] = authorization;
|
|
35
|
+
// console.warn('config.headers:', config.headers);
|
|
36
|
+
return config;
|
|
37
|
+
}, (error) => {
|
|
38
|
+
return Promise.reject(error);
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=api-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-config.js","sourceRoot":"","sources":["../src/api-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;AACjE,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;AAChE,kEAAkE;AAClE,KAAK,CAAC,QAAQ,CAAC,gBAAgB,GAAG;IAChC,UAAU,IAAS,EAAE,OAAY;QAC/B,6BAA6B;QAC7B,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,IAAI,GAAG,EAAE,CAAC;QACZ,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;CACF,CAAC;AAEF,gEAAgE;AAChE,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAC5B,CAAC,MAAM,EAAE,EAAE;IACT,IAAI,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAChD,IAAI,oBAAoB,GAAG,OAAO,CAAC,GAAG,CAAC,kBAA4B,CAAC;IACpE,IAAI,OAAO,GAAG,EAAE,CAAC,CAAC,4FAA4F;IAC9G,IAAI,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC;IAE5B,iHAAiH;IACjH,IAAI,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACnE,yEAAyE;IACzE,IAAI,gBAAgB,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,OAAO,CAAC;IAEnD,IAAI,UAAU,GACZ,aAAa,GAAG,IAAI,GAAG,gBAAgB,GAAG,IAAI,GAAG,UAAU,GAAG,IAAI,GAAG,WAAW,CAAC;IAEnF,IAAI,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9F,IAAI,aAAa,GAAG,gCAAgC,GAAG,IAAI,CAAC;IAE5D,wDAAwD;IACxD,qDAAqD;IACrD,sDAAsD;IACtD,0GAA0G;IAE1G,MAAM,CAAC,OAAO,CAAC,wBAAwB,CAAC,GAAG,aAAa,CAAC;IACzD,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,gBAAgB,CAAC;IAC7C,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,aAAa,CAAC;IAEhD,mDAAmD;IAEnD,OAAO,MAAM,CAAC;AAChB,CAAC,EACD,CAAC,KAAK,EAAE,EAAE;IACR,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC,CACF,CAAC"}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Server generated from OpenAPI spec for convert-api v2.0.0
|
|
4
|
+
* Generated on: 2025-06-05T16:52:41.275Z
|
|
5
|
+
*/
|
|
6
|
+
import './api-config.js';
|
|
7
|
+
/**
|
|
8
|
+
* Server configuration
|
|
9
|
+
*/
|
|
10
|
+
export declare const SERVER_NAME = "convert-api";
|
|
11
|
+
export declare const SERVER_VERSION = "2.0.0";
|
|
12
|
+
export declare const API_BASE_URL = "https://api.convert.com/api/v2";
|
package/build/index.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Server generated from OpenAPI spec for convert-api v2.0.0
|
|
4
|
+
* Generated on: 2025-06-05T16:52:41.275Z
|
|
5
|
+
*/
|
|
6
|
+
// Load environment variables from .env file
|
|
7
|
+
import dotenv from 'dotenv';
|
|
8
|
+
dotenv.config();
|
|
9
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
12
|
+
import { z, ZodError } from 'zod';
|
|
13
|
+
import { jsonSchemaToZod } from 'json-schema-to-zod';
|
|
14
|
+
import axios from 'axios';
|
|
15
|
+
if (process.env.NODE_ENV === 'development') {
|
|
16
|
+
import('mcps-logger/console');
|
|
17
|
+
}
|
|
18
|
+
import './api-config.js';
|
|
19
|
+
import { toolDefinitionMap } from './tools.js';
|
|
20
|
+
function getToolsForClient() {
|
|
21
|
+
const reportingTools = [
|
|
22
|
+
'getaccountslist',
|
|
23
|
+
'getaccountlivedata',
|
|
24
|
+
'getaccountactivecompletedexperienceslist',
|
|
25
|
+
'getprojectslist',
|
|
26
|
+
'getproject',
|
|
27
|
+
'getprojectlivedata',
|
|
28
|
+
'getexperienceslist',
|
|
29
|
+
'getexperience',
|
|
30
|
+
'getexperiencebykey',
|
|
31
|
+
'getexperiencelivedata',
|
|
32
|
+
'getexperienceaggregatedreport',
|
|
33
|
+
'getexperiencedailyreport',
|
|
34
|
+
'getexperiencedailytrafficallocation',
|
|
35
|
+
'getgoal',
|
|
36
|
+
'getgoalbykey',
|
|
37
|
+
'getlocation',
|
|
38
|
+
'getlocationslist',
|
|
39
|
+
'getaudienceslist',
|
|
40
|
+
'getaudience',
|
|
41
|
+
'getfeatureslist',
|
|
42
|
+
'getfeature',
|
|
43
|
+
'getfeaturebykey',
|
|
44
|
+
];
|
|
45
|
+
const toolSet = process.env.TOOLS_FOR_CLIENT ?? 'reporting';
|
|
46
|
+
return Array.from(toolDefinitionMap.values())
|
|
47
|
+
.filter((def) => {
|
|
48
|
+
if (toolSet === 'readOnly') {
|
|
49
|
+
if (def.name.match(/^get/)) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
else if (toolSet === 'reporting') {
|
|
55
|
+
return reportingTools.indexOf(def.name) !== -1;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
})
|
|
59
|
+
.map((def) => {
|
|
60
|
+
return {
|
|
61
|
+
name: def.name,
|
|
62
|
+
description: def.description,
|
|
63
|
+
inputSchema: def.inputSchema,
|
|
64
|
+
};
|
|
65
|
+
})
|
|
66
|
+
.filter((tool) => tool !== null);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Server configuration
|
|
70
|
+
*/
|
|
71
|
+
export const SERVER_NAME = "convert-api";
|
|
72
|
+
export const SERVER_VERSION = "2.0.0";
|
|
73
|
+
export const API_BASE_URL = "https://api.convert.com/api/v2";
|
|
74
|
+
/**
|
|
75
|
+
* MCP Server instance
|
|
76
|
+
*/
|
|
77
|
+
const server = new Server({ name: SERVER_NAME, version: SERVER_VERSION }, { capabilities: { tools: {} } });
|
|
78
|
+
/**
|
|
79
|
+
* Security schemes from the OpenAPI spec
|
|
80
|
+
*/
|
|
81
|
+
const securitySchemes = {};
|
|
82
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
83
|
+
return { tools: getToolsForClient() };
|
|
84
|
+
});
|
|
85
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
86
|
+
const { name: toolName, arguments: toolArgs } = request.params;
|
|
87
|
+
const toolDefinition = toolDefinitionMap.get(toolName);
|
|
88
|
+
if (!toolDefinition) {
|
|
89
|
+
console.error(`Error: Unknown tool requested: ${toolName}`);
|
|
90
|
+
return { content: [{ type: "text", text: `Error: Unknown tool requested: ${toolName}` }] };
|
|
91
|
+
}
|
|
92
|
+
return await executeApiTool(toolName, toolDefinition, toolArgs ?? {}, securitySchemes);
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* Executes an API tool with the provided arguments
|
|
96
|
+
*
|
|
97
|
+
* @param toolName Name of the tool to execute
|
|
98
|
+
* @param definition Tool definition
|
|
99
|
+
* @param toolArgs Arguments provided by the user
|
|
100
|
+
* @param allSecuritySchemes Security schemes from the OpenAPI spec
|
|
101
|
+
* @returns Call tool result
|
|
102
|
+
*/
|
|
103
|
+
async function executeApiTool(toolName, definition, toolArgs, allSecuritySchemes) {
|
|
104
|
+
try {
|
|
105
|
+
// Validate arguments against the input schema
|
|
106
|
+
let validatedArgs;
|
|
107
|
+
try {
|
|
108
|
+
const zodSchema = getZodSchemaFromJsonSchema(definition.inputSchema, toolName);
|
|
109
|
+
const argsToParse = (typeof toolArgs === 'object' && toolArgs !== null) ? toolArgs : {};
|
|
110
|
+
validatedArgs = zodSchema.parse(argsToParse);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (error instanceof ZodError) {
|
|
114
|
+
const validationErrorMessage = `Invalid arguments for tool '${toolName}': ${error.errors.map(e => `${e.path.join('.')} (${e.code}): ${e.message}`).join(', ')}`;
|
|
115
|
+
return { content: [{ type: 'text', text: validationErrorMessage }] };
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
119
|
+
return { content: [{ type: 'text', text: `Internal error during validation setup: ${errorMessage}` }] };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Prepare URL, query parameters, headers, and request body
|
|
123
|
+
let urlPath = definition.pathTemplate;
|
|
124
|
+
const queryParams = {};
|
|
125
|
+
const headers = { 'Accept': 'application/json' };
|
|
126
|
+
let requestBodyData = undefined;
|
|
127
|
+
// Apply parameters to the URL path, query, or headers
|
|
128
|
+
definition.executionParameters.forEach((param) => {
|
|
129
|
+
const value = validatedArgs[param.name];
|
|
130
|
+
if (typeof value !== 'undefined' && value !== null) {
|
|
131
|
+
if (param.in === 'path') {
|
|
132
|
+
urlPath = urlPath.replace(`{${param.name}}`, encodeURIComponent(String(value)));
|
|
133
|
+
}
|
|
134
|
+
else if (param.in === 'query') {
|
|
135
|
+
queryParams[param.name] = value;
|
|
136
|
+
}
|
|
137
|
+
else if (param.in === 'header') {
|
|
138
|
+
headers[param.name.toLowerCase()] = String(value);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
// Ensure all path parameters are resolved
|
|
143
|
+
if (urlPath.includes('{')) {
|
|
144
|
+
throw new Error(`Failed to resolve path parameters: ${urlPath}`);
|
|
145
|
+
}
|
|
146
|
+
// Construct the full URL
|
|
147
|
+
const requestUrl = API_BASE_URL ? `${API_BASE_URL}${urlPath}` : urlPath;
|
|
148
|
+
// Handle request body if needed
|
|
149
|
+
if (definition.requestBodyContentType && typeof validatedArgs['requestBody'] !== 'undefined') {
|
|
150
|
+
requestBodyData = validatedArgs['requestBody'];
|
|
151
|
+
headers['content-type'] = definition.requestBodyContentType;
|
|
152
|
+
}
|
|
153
|
+
// Prepare the axios request configuration
|
|
154
|
+
const config = {
|
|
155
|
+
method: definition.method.toUpperCase(),
|
|
156
|
+
url: requestUrl,
|
|
157
|
+
params: queryParams,
|
|
158
|
+
headers: headers,
|
|
159
|
+
...(requestBodyData !== undefined && { data: requestBodyData }),
|
|
160
|
+
};
|
|
161
|
+
// Log request info to stderr (doesn't affect MCP output)
|
|
162
|
+
console.error(`Executing tool "${toolName}": ${config.method} ${config.url}`);
|
|
163
|
+
// Execute the request
|
|
164
|
+
const response = await axios(config);
|
|
165
|
+
// Process and format the response
|
|
166
|
+
let responseText = '';
|
|
167
|
+
const contentType = response.headers['content-type']?.toLowerCase() || '';
|
|
168
|
+
// Handle JSON responses
|
|
169
|
+
if (contentType.includes('application/json') && typeof response.data === 'object' && response.data !== null) {
|
|
170
|
+
try {
|
|
171
|
+
responseText = JSON.stringify(response.data, null, 2);
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
responseText = "[Stringify Error]";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Handle string responses
|
|
178
|
+
else if (typeof response.data === 'string') {
|
|
179
|
+
responseText = response.data;
|
|
180
|
+
}
|
|
181
|
+
// Handle other response types
|
|
182
|
+
else if (response.data !== undefined && response.data !== null) {
|
|
183
|
+
responseText = String(response.data);
|
|
184
|
+
}
|
|
185
|
+
// Handle empty responses
|
|
186
|
+
else {
|
|
187
|
+
responseText = `(Status: ${response.status} - No body content)`;
|
|
188
|
+
}
|
|
189
|
+
// Return formatted response
|
|
190
|
+
return {
|
|
191
|
+
content: [
|
|
192
|
+
{
|
|
193
|
+
type: "text",
|
|
194
|
+
text: `API Response (Status: ${response.status}):\n${responseText}`
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
// Handle errors during execution
|
|
201
|
+
let errorMessage;
|
|
202
|
+
// Format Axios errors specially
|
|
203
|
+
if (axios.isAxiosError(error)) {
|
|
204
|
+
errorMessage = formatApiError(error);
|
|
205
|
+
}
|
|
206
|
+
// Handle standard errors
|
|
207
|
+
else if (error instanceof Error) {
|
|
208
|
+
errorMessage = error.message;
|
|
209
|
+
}
|
|
210
|
+
// Handle unexpected error types
|
|
211
|
+
else {
|
|
212
|
+
errorMessage = 'Unexpected error: ' + String(error);
|
|
213
|
+
}
|
|
214
|
+
// Log error to stderr
|
|
215
|
+
console.error(`Error during execution of tool '${toolName}':`, errorMessage);
|
|
216
|
+
// Return error message to client
|
|
217
|
+
return { content: [{ type: "text", text: errorMessage }] };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Main function to start the server
|
|
222
|
+
*/
|
|
223
|
+
async function main() {
|
|
224
|
+
// Set up stdio transport
|
|
225
|
+
try {
|
|
226
|
+
const transport = new StdioServerTransport();
|
|
227
|
+
await server.connect(transport);
|
|
228
|
+
console.error(`${SERVER_NAME} MCP Server (v${SERVER_VERSION}) running on stdio${API_BASE_URL ? `, proxying API at ${API_BASE_URL}` : ''}`);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
console.error("Error during server startup:", error);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Cleanup function for graceful shutdown
|
|
237
|
+
*/
|
|
238
|
+
async function cleanup() {
|
|
239
|
+
console.error("Shutting down MCP server...");
|
|
240
|
+
process.exit(0);
|
|
241
|
+
}
|
|
242
|
+
// Register signal handlers
|
|
243
|
+
process.on('SIGINT', cleanup);
|
|
244
|
+
process.on('SIGTERM', cleanup);
|
|
245
|
+
// Start the server
|
|
246
|
+
main().catch((error) => {
|
|
247
|
+
console.error("Fatal error in main execution:", error);
|
|
248
|
+
process.exit(1);
|
|
249
|
+
});
|
|
250
|
+
/**
|
|
251
|
+
* Formats API errors for better readability
|
|
252
|
+
*
|
|
253
|
+
* @param error Axios error
|
|
254
|
+
* @returns Formatted error message
|
|
255
|
+
*/
|
|
256
|
+
function formatApiError(error) {
|
|
257
|
+
let message = 'API request failed.';
|
|
258
|
+
if (error.response) {
|
|
259
|
+
message = `API Error: Status ${error.response.status} (${error.response.statusText || 'Status text not available'}). `;
|
|
260
|
+
const responseData = error.response.data;
|
|
261
|
+
const MAX_LEN = 200;
|
|
262
|
+
if (typeof responseData === 'string') {
|
|
263
|
+
message += `Response: ${responseData.substring(0, MAX_LEN)}${responseData.length > MAX_LEN ? '...' : ''}`;
|
|
264
|
+
}
|
|
265
|
+
else if (responseData) {
|
|
266
|
+
try {
|
|
267
|
+
const jsonString = JSON.stringify(responseData);
|
|
268
|
+
message += `Response: ${jsonString.substring(0, MAX_LEN)}${jsonString.length > MAX_LEN ? '...' : ''}`;
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
message += 'Response: [Could not serialize data]';
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
message += 'No response body received.';
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else if (error.request) {
|
|
279
|
+
message = 'API Network Error: No response received from server.';
|
|
280
|
+
if (error.code)
|
|
281
|
+
message += ` (Code: ${error.code})`;
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
message += `API Request Setup Error: ${error.message}`;
|
|
285
|
+
}
|
|
286
|
+
return message;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Converts a JSON Schema to a Zod schema for runtime validation
|
|
290
|
+
*
|
|
291
|
+
* @param jsonSchema JSON Schema
|
|
292
|
+
* @param toolName Tool name for error reporting
|
|
293
|
+
* @returns Zod schema
|
|
294
|
+
*/
|
|
295
|
+
function getZodSchemaFromJsonSchema(jsonSchema, toolName) {
|
|
296
|
+
if (typeof jsonSchema !== 'object' || jsonSchema === null) {
|
|
297
|
+
return z.object({}).passthrough();
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
const zodSchemaString = jsonSchemaToZod(jsonSchema);
|
|
301
|
+
const zodSchema = eval(zodSchemaString);
|
|
302
|
+
if (typeof zodSchema?.parse !== 'function') {
|
|
303
|
+
throw new Error('Eval did not produce a valid Zod schema.');
|
|
304
|
+
}
|
|
305
|
+
return zodSchema;
|
|
306
|
+
}
|
|
307
|
+
catch (err) {
|
|
308
|
+
console.error(`Failed to generate/evaluate Zod schema for '${toolName}':`, err);
|
|
309
|
+
return z.object({}).passthrough();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=index.js.map
|