@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 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"}
@@ -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