mcp-zenskar 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.
- package/.github/workflows/npm-publish.yml +50 -0
- package/INSTALL.md +115 -0
- package/README.md +129 -0
- package/package.json +42 -0
- package/src/mcp-config.json +4587 -0
- package/src/response-processor.js +26 -0
- package/src/server.js +229 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Simple response processor for Zenskar API responses
|
|
2
|
+
export default class ResponseProcessor {
|
|
3
|
+
constructor() {
|
|
4
|
+
// Minimal configuration
|
|
5
|
+
this.maxResponseLength = 50000;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
processResponse(responseData, toolName) {
|
|
9
|
+
try {
|
|
10
|
+
// Convert response to string if needed
|
|
11
|
+
let response = typeof responseData === 'string'
|
|
12
|
+
? responseData
|
|
13
|
+
: JSON.stringify(responseData, null, 2);
|
|
14
|
+
|
|
15
|
+
// Simple truncation if too long
|
|
16
|
+
if (response.length > this.maxResponseLength) {
|
|
17
|
+
response = response.substring(0, this.maxResponseLength) + '\n\n[Response truncated due to length]';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return response;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('Response processing error:', error);
|
|
23
|
+
return 'Error processing response';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/server.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
// Get __dirname equivalent for ES modules
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
|
|
14
|
+
// Import the response processor
|
|
15
|
+
import ResponseProcessor from './response-processor.js';
|
|
16
|
+
|
|
17
|
+
// Load the configuration
|
|
18
|
+
const configPath = path.join(__dirname, 'mcp-config.json');
|
|
19
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
20
|
+
|
|
21
|
+
// Logger configuration
|
|
22
|
+
const logger = {
|
|
23
|
+
info: (message, data) => console.error(`[INFO] ${message}`, data || ''),
|
|
24
|
+
error: (message, data) => console.error(`[ERROR] ${message}`, data || ''),
|
|
25
|
+
warn: (message, data) => console.error(`[WARN] ${message}`, data || ''),
|
|
26
|
+
debug: (message, data) => console.error(`[DEBUG] ${message}`, data || '')
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// User context validation schema
|
|
30
|
+
const userContextSchema = z.object({
|
|
31
|
+
organization: z.string().describe('Organization ID for multi-tenant API access'),
|
|
32
|
+
authorization: z.string().describe('Bearer token for API authentication'),
|
|
33
|
+
}).describe('Required authentication context for Zenskar API');
|
|
34
|
+
|
|
35
|
+
class ZenskarMcpServer {
|
|
36
|
+
constructor() {
|
|
37
|
+
this.server = new McpServer({
|
|
38
|
+
name: config.server.name,
|
|
39
|
+
version: "1.0.0"
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
this.responseProcessor = new ResponseProcessor();
|
|
43
|
+
this.setupTools();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setupTools() {
|
|
47
|
+
// Register each tool from the configuration
|
|
48
|
+
config.tools.forEach(tool => {
|
|
49
|
+
this.server.registerTool(tool.name, {
|
|
50
|
+
description: tool.description,
|
|
51
|
+
inputSchema: this.generateInputSchema(tool)
|
|
52
|
+
}, async (args) => {
|
|
53
|
+
try {
|
|
54
|
+
return await this.executeTool(tool, args);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
logger.error(`Tool execution failed for ${tool.name}:`, error.message);
|
|
57
|
+
return {
|
|
58
|
+
content: [
|
|
59
|
+
{
|
|
60
|
+
type: "text",
|
|
61
|
+
text: `Error: ${error.message}`
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
generateInputSchema(tool) {
|
|
71
|
+
const schemaObj = {
|
|
72
|
+
organization: z.string().describe("Organization ID for multi-tenant API access (required)"),
|
|
73
|
+
authorization: z.string().describe("Bearer token for API authentication (required)")
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Add tool-specific arguments
|
|
77
|
+
tool.args.forEach(arg => {
|
|
78
|
+
let schema;
|
|
79
|
+
switch(arg.type) {
|
|
80
|
+
case 'string':
|
|
81
|
+
schema = z.string();
|
|
82
|
+
break;
|
|
83
|
+
case 'integer':
|
|
84
|
+
case 'number':
|
|
85
|
+
schema = z.number();
|
|
86
|
+
break;
|
|
87
|
+
case 'boolean':
|
|
88
|
+
schema = z.boolean();
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
schema = z.string();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
schema = schema.describe(arg.description);
|
|
95
|
+
|
|
96
|
+
if (!arg.required) {
|
|
97
|
+
schema = schema.optional();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
schemaObj[arg.name] = schema;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return schemaObj;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async executeTool(tool, args) {
|
|
107
|
+
// Extract authentication from arguments
|
|
108
|
+
const { organization, authorization, ...toolArgs } = args;
|
|
109
|
+
|
|
110
|
+
// Validate required authentication
|
|
111
|
+
if (!organization) {
|
|
112
|
+
throw new Error('Organization ID is required for API access');
|
|
113
|
+
}
|
|
114
|
+
if (!authorization) {
|
|
115
|
+
throw new Error('Authorization token is required for API access');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
logger.info(`[${tool.name}] Executing with organization: ${organization.substring(0, 10)}...`);
|
|
119
|
+
|
|
120
|
+
// Build headers
|
|
121
|
+
const headers = {
|
|
122
|
+
'Content-Type': 'application/json',
|
|
123
|
+
'Accept': 'application/json',
|
|
124
|
+
'organisation': organization, // Note: API uses 'organisation' not 'organization'
|
|
125
|
+
'Authorization': authorization.startsWith('Bearer ') ? authorization : `Bearer ${authorization}`
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Build URL from requestTemplate
|
|
129
|
+
let url = `${config.server.baseUrl}${tool.requestTemplate.url}`;
|
|
130
|
+
const method = tool.requestTemplate.method;
|
|
131
|
+
|
|
132
|
+
// Add path parameters
|
|
133
|
+
tool.args.forEach(arg => {
|
|
134
|
+
if (arg.position === 'path' && toolArgs[arg.name]) {
|
|
135
|
+
url = url.replace(`{${arg.name}}`, toolArgs[arg.name]);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Add query parameters
|
|
140
|
+
const queryParams = new URLSearchParams();
|
|
141
|
+
tool.args.forEach(arg => {
|
|
142
|
+
if (arg.position === 'query' && toolArgs[arg.name] !== undefined) {
|
|
143
|
+
queryParams.append(arg.name, toolArgs[arg.name]);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (queryParams.toString()) {
|
|
148
|
+
url += `?${queryParams.toString()}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Prepare request body for POST/PUT/PATCH requests
|
|
152
|
+
let body = null;
|
|
153
|
+
if (['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) {
|
|
154
|
+
const bodyArgs = {};
|
|
155
|
+
tool.args.forEach(arg => {
|
|
156
|
+
if (arg.position === 'body' && toolArgs[arg.name] !== undefined) {
|
|
157
|
+
bodyArgs[arg.name] = toolArgs[arg.name];
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
if (Object.keys(bodyArgs).length > 0) {
|
|
162
|
+
body = JSON.stringify(bodyArgs);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
logger.debug(`[${tool.name}] Making ${method} request to: ${url}`);
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const response = await fetch(url, {
|
|
170
|
+
method: method,
|
|
171
|
+
headers,
|
|
172
|
+
body
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const responseText = await response.text();
|
|
176
|
+
|
|
177
|
+
if (!response.ok) {
|
|
178
|
+
logger.error(`[${tool.name}] HTTP ${response.status}: ${responseText}`);
|
|
179
|
+
throw new Error(`HTTP ${response.status}: ${responseText}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Process response
|
|
183
|
+
let responseData;
|
|
184
|
+
try {
|
|
185
|
+
responseData = JSON.parse(responseText);
|
|
186
|
+
} catch {
|
|
187
|
+
responseData = responseText;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Use response processor for formatting
|
|
191
|
+
const processedResponse = this.responseProcessor.processResponse(responseData, tool.name);
|
|
192
|
+
|
|
193
|
+
logger.info(`[${tool.name}] Request successful`);
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
content: [
|
|
197
|
+
{
|
|
198
|
+
type: "text",
|
|
199
|
+
text: processedResponse
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
} catch (error) {
|
|
205
|
+
const errorMessage = `Error executing ${tool.name}: ${error.message}\n\nThis might be due to:\n- Invalid parameters\n- API rate limiting\n- Network connectivity issues\n- Authentication problems (check organization ID and Bearer token)\n\nPlease verify your credentials and try again.`;
|
|
206
|
+
|
|
207
|
+
logger.error(`[${tool.name}] Request failed: ${error.message}`);
|
|
208
|
+
|
|
209
|
+
throw new Error(errorMessage);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async run() {
|
|
214
|
+
const transport = new StdioServerTransport();
|
|
215
|
+
await this.server.connect(transport);
|
|
216
|
+
logger.info(`${config.server.name} MCP server running`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Start the server
|
|
221
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
222
|
+
const server = new ZenskarMcpServer();
|
|
223
|
+
server.run().catch(error => {
|
|
224
|
+
logger.error('Failed to start server:', error);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export default ZenskarMcpServer;
|