mcp-zenskar 1.0.13 → 1.0.14

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 (2) hide show
  1. package/package.json +1 -1
  2. package/test-mcp.mjs +0 -296
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-zenskar",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
4
4
  "description": "Model Context Protocol (MCP) server for Zenskar API - customer management, invoicing, and billing operations",
5
5
  "main": "src/server.js",
6
6
  "bin": {
package/test-mcp.mjs DELETED
@@ -1,296 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * MCP Server Integration Test
4
- * Tests the Zenskar MCP server via the actual MCP protocol (stdio transport).
5
- */
6
- import { Client } from '@modelcontextprotocol/sdk/client/index.js';
7
- import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
8
-
9
- const ORG_ID = '888ae523-8878-4ed7-85cc-6c0a54320568';
10
- const API_KEY = 'sandbox_yA9yieTXSqnGxROOoyLMWIP2CTuCm5j_311CVHu16-U';
11
-
12
- const userContext = {
13
- organization: ORG_ID,
14
- authorization: API_KEY,
15
- };
16
-
17
- let client;
18
- let passed = 0;
19
- let failed = 0;
20
- const failures = [];
21
-
22
- async function callTool(name, args = {}) {
23
- const result = await client.callTool({
24
- name,
25
- arguments: { ...args, __userContext: userContext },
26
- });
27
- // MCP returns content array; extract text
28
- const text = result.content?.map(c => c.text).join('') || '';
29
- const json = (() => { try { return JSON.parse(text); } catch { return null; } })();
30
- return { text, json, raw: result };
31
- }
32
-
33
- async function test(label, fn) {
34
- try {
35
- await fn();
36
- passed++;
37
- console.log(` ✅ ${label}`);
38
- } catch (err) {
39
- failed++;
40
- failures.push({ label, error: err.message || String(err) });
41
- console.log(` ❌ ${label}: ${err.message || err}`);
42
- }
43
- }
44
-
45
- function assert(cond, msg) {
46
- if (!cond) throw new Error(msg);
47
- }
48
-
49
- // ─── MAIN ────────────────────────────────────────────────────
50
- async function main() {
51
- console.log('Starting MCP server via stdio...\n');
52
-
53
- const transport = new StdioClientTransport({
54
- command: 'node',
55
- args: ['src/server.js'],
56
- env: { ...process.env, ZENSKAR_ORGANIZATION: ORG_ID, ZENSKAR_API_KEY: API_KEY },
57
- });
58
-
59
- client = new Client({ name: 'test-client', version: '1.0.0' });
60
- await client.connect(transport);
61
-
62
- // List available tools
63
- const { tools } = await client.listTools();
64
- console.log(`MCP server connected. ${tools.length} tools registered.\n`);
65
-
66
- // ─── CUSTOMERS ──────────────────────────────────────────
67
- console.log('── Customers ──');
68
-
69
- let customerId;
70
- await test('listCustomers (basic)', async () => {
71
- const { json } = await callTool('listCustomers', { limit: 2 });
72
- const resp = json?.api_response || json;
73
- assert(resp?.results?.length > 0, 'No results returned');
74
- assert(resp.total_count > 0, 'total_count missing');
75
- customerId = resp.results[0].id;
76
- });
77
-
78
- await test('listCustomers (search field=value)', async () => {
79
- const { json } = await callTool('listCustomers', { search: 'customer_name=Star' });
80
- const resp = json?.api_response || json;
81
- assert(resp?.total_count <= 10, `Expected filtered results, got ${resp?.total_count}`);
82
- });
83
-
84
- await test('listCustomers (search_name_external_id)', async () => {
85
- const { json } = await callTool('listCustomers', { search_name_external_id: 'Star', limit: 5 });
86
- const resp = json?.api_response || json;
87
- assert(resp?.total_count <= 10, `Expected filtered results, got ${resp?.total_count}`);
88
- });
89
-
90
- await test('getCustomerById', async () => {
91
- if (!customerId) throw new Error('No customer ID from previous test');
92
- const { json } = await callTool('getCustomerById', { customerId });
93
- const resp = json?.api_response || json;
94
- assert(resp?.id === customerId, 'ID mismatch');
95
- });
96
-
97
- // ─── CONTACTS ──────────────────────────────────────────
98
- console.log('\n── Contacts ──');
99
-
100
- let contactId;
101
- await test('listContacts', async () => {
102
- const { json } = await callTool('listContacts', { limit: 2 });
103
- const resp = json?.api_response || json;
104
- assert(resp?.results?.length > 0, 'No contacts');
105
- contactId = resp.results[0].id;
106
- });
107
-
108
- await test('getContactById', async () => {
109
- if (!contactId) throw new Error('No contact ID');
110
- const { json } = await callTool('getContactById', { contactId });
111
- const resp = json?.api_response || json;
112
- assert(resp?.id === contactId, 'ID mismatch');
113
- });
114
-
115
- // ─── INVOICES ──────────────────────────────────────────
116
- console.log('\n── Invoices ──');
117
-
118
- let invoiceId;
119
- await test('listInvoices (basic)', async () => {
120
- const { json } = await callTool('listInvoices', { limit: 2 });
121
- const resp = json?.api_response || json;
122
- assert(resp?.results?.length > 0, 'No invoices');
123
- invoiceId = resp.results[0].id;
124
- });
125
-
126
- await test('listInvoices (customer_id filter)', async () => {
127
- if (!customerId) throw new Error('No customer ID');
128
- const { json } = await callTool('listInvoices', { customer_id: customerId, limit: 1 });
129
- const resp = json?.api_response || json;
130
- // Should filter (may return 0 if customer has no invoices, but should not error)
131
- assert(resp?.total_count !== undefined, 'Missing total_count');
132
- });
133
-
134
- await test('listInvoices (invoice_total__gte filter)', async () => {
135
- const { json } = await callTool('listInvoices', { invoice_total__gte: 100, limit: 1 });
136
- const resp = json?.api_response || json;
137
- assert(resp?.total_count !== undefined, 'Missing total_count');
138
- });
139
-
140
- await test('getInvoiceById', async () => {
141
- if (!invoiceId) throw new Error('No invoice ID');
142
- const { json } = await callTool('getInvoiceById', { invoiceId });
143
- const resp = json?.api_response || json;
144
- assert(resp?.id === invoiceId, 'ID mismatch');
145
- });
146
-
147
- await test('getInvoiceLineItems', async () => {
148
- if (!invoiceId) throw new Error('No invoice ID');
149
- const { json } = await callTool('getInvoiceLineItems', { invoiceId });
150
- const resp = json?.api_response || json;
151
- assert(resp !== null, 'No response');
152
- });
153
-
154
- await test('getInvoiceSummary', async () => {
155
- if (!invoiceId) throw new Error('No invoice ID');
156
- const { json } = await callTool('getInvoiceSummary', { invoiceId });
157
- const resp = json?.api_response || json;
158
- assert(resp !== null, 'No response');
159
- });
160
-
161
- await test('getAllInvoiceTags', async () => {
162
- const { json } = await callTool('getAllInvoiceTags');
163
- const resp = json?.api_response || json;
164
- assert(resp?.tags, 'No tags returned');
165
- });
166
-
167
- // ─── PAYMENTS ──────────────────────────────────────────
168
- console.log('\n── Payments ──');
169
-
170
- let paymentId;
171
- await test('listAllPayments (basic)', async () => {
172
- const { json } = await callTool('listAllPayments', { limit: 2 });
173
- const resp = json?.api_response || json;
174
- assert(resp?.results?.length > 0, 'No payments');
175
- paymentId = resp.results[0].id;
176
- });
177
-
178
- await test('listAllPayments (sort_key + payment_method filter)', async () => {
179
- const { json } = await callTool('listAllPayments', { sort_key: 'created_at', sort_type: 'DESC', payment_method: 'bank_transfer', limit: 1 });
180
- const resp = json?.api_response || json;
181
- assert(resp?.total_count !== undefined, 'Missing total_count');
182
- });
183
-
184
- await test('getPaymentById', async () => {
185
- if (!paymentId) throw new Error('No payment ID');
186
- const { json } = await callTool('getPaymentById', { paymentId });
187
- const resp = json?.api_response || json;
188
- assert(resp?.id === paymentId, 'ID mismatch');
189
- });
190
-
191
- // ─── CONTRACTS ──────────────────────────────────────────
192
- console.log('\n── Contracts ──');
193
-
194
- await test('listContracts (basic)', async () => {
195
- const { json } = await callTool('listContracts', { limit: 2 });
196
- const resp = json?.api_response || json;
197
- assert(resp?.results?.length > 0, 'No contracts');
198
- });
199
-
200
- await test('listContracts (status filter)', async () => {
201
- const { json } = await callTool('listContracts', { status: 'active', limit: 1 });
202
- const resp = json?.api_response || json;
203
- assert(resp?.total_count !== undefined, 'Missing total_count');
204
- });
205
-
206
- // ─── AGGREGATES (Billable Metrics) ──────────────────────
207
- console.log('\n── Billable Metrics ──');
208
-
209
- let aggregateId;
210
- await test('listAggregates', async () => {
211
- const { json } = await callTool('listAggregates', { limit: 2 });
212
- const resp = json?.api_response || json;
213
- assert(resp?.results?.length > 0, 'No aggregates');
214
- aggregateId = resp.results[0].id;
215
- });
216
-
217
- await test('getAggregateById', async () => {
218
- if (!aggregateId) throw new Error('No aggregate ID');
219
- const { json } = await callTool('getAggregateById', { aggregateId });
220
- const resp = json?.api_response || json;
221
- assert(resp?.id === aggregateId, 'ID mismatch');
222
- });
223
-
224
- await test('getAggregateSchemas', async () => {
225
- const { json } = await callTool('getAggregateSchemas');
226
- const resp = json?.api_response || json;
227
- assert(Array.isArray(resp), 'Expected array of schemas');
228
- });
229
-
230
- await test('getAggregateEstimates (snake_case params)', async () => {
231
- if (!aggregateId || !customerId) throw new Error('Missing IDs');
232
- // This may return 404 domain error (no data) but should NOT 422
233
- try {
234
- const { json } = await callTool('getAggregateEstimates', {
235
- aggregate_id: aggregateId,
236
- customer_id: customerId,
237
- start_date: '2025-01-01',
238
- end_date: '2025-12-31',
239
- });
240
- // If we get here, great
241
- } catch (e) {
242
- // 404 domain error is OK (AGGREGATE_ESTIMATE_NOT_FOUND), 422 is NOT
243
- if (e.message?.includes('422')) throw new Error('Got 422 — snake_case params not working');
244
- }
245
- });
246
-
247
- // ─── RAW METRICS ──────────────────────────────────────
248
- console.log('\n── Raw Metrics ──');
249
-
250
- let rawMetricSlug;
251
- await test('listRawMetrics', async () => {
252
- const { json } = await callTool('listRawMetrics', { limit: 2 });
253
- const resp = json?.api_response || json;
254
- assert(resp?.results?.length > 0, 'No raw metrics');
255
- rawMetricSlug = resp.results[0].api_slug;
256
- });
257
-
258
- await test('getRawMetricBySlug', async () => {
259
- if (!rawMetricSlug) throw new Error('No slug');
260
- const { json } = await callTool('getRawMetricBySlug', { rawMetricSlug });
261
- const resp = json?.api_response || json;
262
- assert(resp?.api_slug === rawMetricSlug, 'Slug mismatch');
263
- });
264
-
265
- // ─── OTHER TOOLS ──────────────────────────────────────
266
- console.log('\n── Other Tools ──');
267
-
268
- await test('getCurrentDateTime', async () => {
269
- const { json } = await callTool('getCurrentDateTime');
270
- const resp = json?.api_response || json;
271
- assert(resp?.currentDate || resp?.currentDateTime, 'No date returned');
272
- });
273
-
274
- await test('getCustomerPortalConfiguration', async () => {
275
- const { json } = await callTool('getCustomerPortalConfiguration');
276
- const resp = json?.api_response || json;
277
- assert(resp !== null, 'No response');
278
- });
279
-
280
- // ─── SUMMARY ──────────────────────────────────────────
281
- console.log(`\n${'═'.repeat(50)}`);
282
- console.log(`RESULTS: ${passed} passed, ${failed} failed out of ${passed + failed} tests`);
283
- if (failures.length > 0) {
284
- console.log('\nFailed tests:');
285
- failures.forEach(f => console.log(` ❌ ${f.label}: ${f.error}`));
286
- }
287
- console.log(`${'═'.repeat(50)}\n`);
288
-
289
- await client.close();
290
- process.exit(failed > 0 ? 1 : 0);
291
- }
292
-
293
- main().catch(err => {
294
- console.error('Fatal error:', err);
295
- process.exit(1);
296
- });