@nekzus/mcp-server 1.0.33 → 1.0.35

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 CHANGED
@@ -79,6 +79,129 @@ Gets the current date and time for a specific timezone.
79
79
  }
80
80
  ```
81
81
 
82
+ ### 4. calculator
83
+
84
+ Performs mathematical calculations with support for basic and advanced operations.
85
+
86
+ **Parameters:**
87
+
88
+ - `expression` (string): Mathematical expression (e.g., "2 + 2 * 3")
89
+ - `precision` (number, optional): Decimal places in the result (default: 2)
90
+
91
+ **Example:**
92
+
93
+ ```typescript
94
+ // Result: 8
95
+ {
96
+ expression: "2 + 2 * 3"
97
+ }
98
+ ```
99
+
100
+ ### 5. passwordGen
101
+
102
+ Generates secure passwords with customizable options.
103
+
104
+ **Parameters:**
105
+
106
+ - `length` (number, optional): Password length (default: 16)
107
+ - `includeNumbers` (boolean, optional): Include numbers (default: true)
108
+ - `includeSymbols` (boolean, optional): Include special characters (default: true)
109
+ - `includeUppercase` (boolean, optional): Include uppercase letters (default: true)
110
+
111
+ **Example:**
112
+
113
+ ```typescript
114
+ // Result: 4v7&9G8$
115
+ {
116
+ length: 16,
117
+ includeNumbers: true,
118
+ includeSymbols: true,
119
+ includeUppercase: true
120
+ }
121
+ ```
122
+
123
+ ### 6. qrGen
124
+
125
+ Generates QR codes for text or URLs.
126
+
127
+ **Parameters:**
128
+
129
+ - `text` (string): Text or URL to encode
130
+ - `size` (number, optional): Size in pixels (default: 200)
131
+ - `dark` (string, optional): Dark module color (default: "#000000")
132
+ - `light` (string, optional): Light module color (default: "#ffffff")
133
+
134
+ **Example:**
135
+
136
+ ```typescript
137
+ // Result: QR code for "https://example.com"
138
+ {
139
+ text: "https://example.com"
140
+ }
141
+ ```
142
+
143
+ ### 7. kitchenConvert
144
+
145
+ Converts between common kitchen measurements and weights, including volume-to-weight conversions based on specific ingredients.
146
+
147
+ **Parameters:**
148
+
149
+ - `value` (number): Value to convert
150
+ - `from` (string): Source unit (e.g., "cup", "tbsp", "g", "oz", "ml")
151
+ - `to` (string): Target unit (e.g., "cup", "tbsp", "g", "oz", "ml")
152
+ - `ingredient` (string, optional): Ingredient for accurate volume-to-weight conversions
153
+
154
+ **Supported Units:**
155
+
156
+ *Volume:*
157
+ - ml (milliliters)
158
+ - l (liters)
159
+ - cup (US cup)
160
+ - tbsp (tablespoon)
161
+ - tsp (teaspoon)
162
+ - floz (fluid ounce)
163
+
164
+ *Weight:*
165
+ - g (grams)
166
+ - kg (kilograms)
167
+ - oz (ounces)
168
+ - lb (pounds)
169
+
170
+ **Supported Ingredients:**
171
+ - water
172
+ - milk
173
+ - flour
174
+ - sugar
175
+ - brown sugar
176
+ - salt
177
+ - butter
178
+ - oil
179
+ - honey
180
+ - maple syrup
181
+
182
+ **Examples:**
183
+
184
+ ```typescript
185
+ // Simple volume conversion
186
+ // Result: šŸ”„ Conversion Result:
187
+ // • 1 cup = 236.59 ml
188
+ {
189
+ value: 1,
190
+ from: "cup",
191
+ to: "ml"
192
+ }
193
+
194
+ // Volume to weight conversion with ingredient
195
+ // Result: šŸ”„ Conversion Result:
196
+ // • 1 cup of flour = 140.25 g
197
+ {
198
+ value: 1,
199
+ from: "cup",
200
+ to: "g",
201
+ ingredient: "flour"
202
+ }
203
+ ```
204
+
82
205
  ## šŸš€ Usage
83
206
 
84
207
  ### As MCP Server
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
package/dist/index.js CHANGED
@@ -2,6 +2,10 @@
2
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
+ import 'dotenv/config';
6
+ // Logger function that uses stderr
7
+ const log = (...args) => console.error(...args);
8
+ // Define the tools once to avoid repetition
5
9
  const TOOLS = [
6
10
  {
7
11
  name: 'greeting',
@@ -22,13 +26,7 @@ const TOOLS = [
22
26
  description: 'Draw a random card from a standard 52-card poker deck',
23
27
  inputSchema: {
24
28
  type: 'object',
25
- properties: {
26
- random_string: {
27
- type: 'string',
28
- description: 'Dummy parameter for no-parameter tools',
29
- },
30
- },
31
- required: ['random_string'],
29
+ properties: {},
32
30
  },
33
31
  },
34
32
  {
@@ -48,29 +46,146 @@ const TOOLS = [
48
46
  },
49
47
  },
50
48
  },
49
+ {
50
+ name: 'calculator',
51
+ description: 'Perform mathematical calculations with support for basic and advanced operations',
52
+ inputSchema: {
53
+ type: 'object',
54
+ properties: {
55
+ expression: {
56
+ type: 'string',
57
+ description: 'Mathematical expression to evaluate (e.g., "2 + 2 * 3")',
58
+ },
59
+ precision: {
60
+ type: 'number',
61
+ description: 'Number of decimal places for the result (default: 2)',
62
+ },
63
+ },
64
+ required: ['expression'],
65
+ },
66
+ },
67
+ {
68
+ name: 'passwordGen',
69
+ description: 'Generate a secure password with customizable options',
70
+ inputSchema: {
71
+ type: 'object',
72
+ properties: {
73
+ length: {
74
+ type: 'number',
75
+ description: 'Length of the password (default: 16)',
76
+ },
77
+ includeNumbers: {
78
+ type: 'boolean',
79
+ description: 'Include numbers in the password (default: true)',
80
+ },
81
+ includeSymbols: {
82
+ type: 'boolean',
83
+ description: 'Include special symbols in the password (default: true)',
84
+ },
85
+ includeUppercase: {
86
+ type: 'boolean',
87
+ description: 'Include uppercase letters in the password (default: true)',
88
+ },
89
+ },
90
+ },
91
+ },
92
+ {
93
+ name: 'qrGen',
94
+ description: 'Generate a QR code for the given text or URL',
95
+ inputSchema: {
96
+ type: 'object',
97
+ properties: {
98
+ text: {
99
+ type: 'string',
100
+ description: 'Text or URL to encode in the QR code',
101
+ },
102
+ size: {
103
+ type: 'number',
104
+ description: 'Size of the QR code in pixels (default: 200)',
105
+ },
106
+ dark: {
107
+ type: 'string',
108
+ description: 'Color for dark modules (default: "#000000")',
109
+ },
110
+ light: {
111
+ type: 'string',
112
+ description: 'Color for light modules (default: "#ffffff")',
113
+ },
114
+ },
115
+ required: ['text'],
116
+ },
117
+ },
118
+ {
119
+ name: 'kitchenConvert',
120
+ description: 'Convert between common kitchen measurements and weights',
121
+ inputSchema: {
122
+ type: 'object',
123
+ properties: {
124
+ value: {
125
+ type: 'number',
126
+ description: 'Value to convert',
127
+ },
128
+ from: {
129
+ type: 'string',
130
+ description: 'Source unit (e.g., "cup", "tbsp", "g", "oz", "ml")',
131
+ },
132
+ to: {
133
+ type: 'string',
134
+ description: 'Target unit (e.g., "cup", "tbsp", "g", "oz", "ml")',
135
+ },
136
+ ingredient: {
137
+ type: 'string',
138
+ description: 'Optional ingredient for accurate volume-to-weight conversions',
139
+ },
140
+ },
141
+ required: ['value', 'from', 'to'],
142
+ },
143
+ },
51
144
  ];
145
+ // Tool handlers
52
146
  async function handleGreeting(args) {
53
147
  const { name } = args;
54
148
  return {
55
149
  content: [
56
150
  {
57
151
  type: 'text',
58
- text: `”Hola ${name}! ¿Cómo estÔs?`,
152
+ text: `šŸ‘‹ Hello ${name}! Welcome to the MCP server!`,
59
153
  },
60
154
  ],
61
155
  isError: false,
62
156
  };
63
157
  }
64
158
  async function handleCard() {
65
- const suits = ['ā™ ', '♄', '♦', '♣'];
66
- const values = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'];
67
- const suit = suits[Math.floor(Math.random() * suits.length)];
68
- const value = values[Math.floor(Math.random() * values.length)];
159
+ const suits = {
160
+ 'ā™ ': 'Spades',
161
+ '♄': 'Hearts',
162
+ '♦': 'Diamonds',
163
+ '♣': 'Clubs',
164
+ };
165
+ const values = {
166
+ A: 'Ace',
167
+ '2': 'Two',
168
+ '3': 'Three',
169
+ '4': 'Four',
170
+ '5': 'Five',
171
+ '6': 'Six',
172
+ '7': 'Seven',
173
+ '8': 'Eight',
174
+ '9': 'Nine',
175
+ '10': 'Ten',
176
+ J: 'Jack',
177
+ Q: 'Queen',
178
+ K: 'King',
179
+ };
180
+ const suitSymbols = Object.keys(suits);
181
+ const valueSymbols = Object.keys(values);
182
+ const suitSymbol = suitSymbols[Math.floor(Math.random() * suitSymbols.length)];
183
+ const valueSymbol = valueSymbols[Math.floor(Math.random() * valueSymbols.length)];
69
184
  return {
70
185
  content: [
71
186
  {
72
187
  type: 'text',
73
- text: `${value}${suit}`,
188
+ text: `šŸŽ“ You drew: ${values[valueSymbol]} of ${suitSymbol} ${suits[suitSymbol]}`,
74
189
  },
75
190
  ],
76
191
  isError: false,
@@ -80,16 +195,21 @@ async function handleDateTime(args) {
80
195
  const { timeZone = 'UTC', locale = 'en-US' } = args;
81
196
  try {
82
197
  const date = new Date();
83
- const formattedDate = new Intl.DateTimeFormat(locale, {
198
+ const dateFormatter = new Intl.DateTimeFormat(locale, {
199
+ timeZone,
200
+ dateStyle: 'long',
201
+ });
202
+ const timeFormatter = new Intl.DateTimeFormat(locale, {
84
203
  timeZone,
85
- dateStyle: 'full',
86
- timeStyle: 'long',
87
- }).format(date);
204
+ timeStyle: 'medium',
205
+ });
206
+ const formattedDate = dateFormatter.format(date);
207
+ const formattedTime = timeFormatter.format(date);
88
208
  return {
89
209
  content: [
90
210
  {
91
211
  type: 'text',
92
- text: formattedDate,
212
+ text: `šŸ—“ļø Date: ${formattedDate}\nā° Time: ${formattedTime}\nšŸŒ Timezone: ${timeZone}`,
93
213
  },
94
214
  ],
95
215
  isError: false,
@@ -107,6 +227,234 @@ async function handleDateTime(args) {
107
227
  };
108
228
  }
109
229
  }
230
+ // New tool handlers
231
+ async function handleCalculator(args) {
232
+ const { expression, precision = 2 } = args;
233
+ try {
234
+ // Sanitize and validate the expression
235
+ const sanitizedExpression = expression.replace(/[^0-9+\-*/().%\s]/g, '');
236
+ if (sanitizedExpression !== expression) {
237
+ throw new Error('Invalid characters in expression');
238
+ }
239
+ // Use Function constructor instead of eval for better security
240
+ const calculate = new Function(`return ${sanitizedExpression}`);
241
+ const result = calculate();
242
+ if (typeof result !== 'number' || !Number.isFinite(result)) {
243
+ throw new Error('Invalid mathematical expression');
244
+ }
245
+ return {
246
+ content: [
247
+ {
248
+ type: 'text',
249
+ text: `🧮 Expression: ${expression}\nšŸ“Š Result: ${result.toFixed(precision)}`,
250
+ },
251
+ ],
252
+ isError: false,
253
+ };
254
+ }
255
+ catch (error) {
256
+ return {
257
+ content: [
258
+ {
259
+ type: 'text',
260
+ text: `āŒ Error: ${error instanceof Error ? error.message : 'Invalid expression'}`,
261
+ },
262
+ ],
263
+ isError: true,
264
+ };
265
+ }
266
+ }
267
+ async function handlePasswordGen(args) {
268
+ const { length = 16, includeNumbers = true, includeSymbols = true, includeUppercase = true, } = args;
269
+ try {
270
+ if (length < 8 || length > 128) {
271
+ throw new Error('Password length must be between 8 and 128 characters');
272
+ }
273
+ const lowercase = 'abcdefghijklmnopqrstuvwxyz';
274
+ const uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
275
+ const numbers = '0123456789';
276
+ const symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?';
277
+ let chars = lowercase;
278
+ if (includeUppercase)
279
+ chars += uppercase;
280
+ if (includeNumbers)
281
+ chars += numbers;
282
+ if (includeSymbols)
283
+ chars += symbols;
284
+ let password = '';
285
+ for (let i = 0; i < length; i++) {
286
+ password += chars.charAt(Math.floor(Math.random() * chars.length));
287
+ }
288
+ // Ensure at least one character from each selected type
289
+ const types = [
290
+ { char: lowercase.charAt(Math.floor(Math.random() * lowercase.length)), condition: true },
291
+ {
292
+ char: uppercase.charAt(Math.floor(Math.random() * uppercase.length)),
293
+ condition: includeUppercase,
294
+ },
295
+ {
296
+ char: numbers.charAt(Math.floor(Math.random() * numbers.length)),
297
+ condition: includeNumbers,
298
+ },
299
+ {
300
+ char: symbols.charAt(Math.floor(Math.random() * symbols.length)),
301
+ condition: includeSymbols,
302
+ },
303
+ ];
304
+ types.forEach(({ char, condition }, index) => {
305
+ if (condition) {
306
+ const pos = Math.floor(Math.random() * length);
307
+ password = password.slice(0, pos) + char + password.slice(pos + 1);
308
+ }
309
+ });
310
+ return {
311
+ content: [
312
+ {
313
+ type: 'text',
314
+ text: `šŸ” Generated Password:\n${password}\n\nšŸ“‹ Password Properties:\n• Length: ${length}\n• Includes Numbers: ${includeNumbers ? 'āœ…' : 'āŒ'}\n• Includes Symbols: ${includeSymbols ? 'āœ…' : 'āŒ'}\n• Includes Uppercase: ${includeUppercase ? 'āœ…' : 'āŒ'}`,
315
+ },
316
+ ],
317
+ isError: false,
318
+ };
319
+ }
320
+ catch (error) {
321
+ return {
322
+ content: [
323
+ {
324
+ type: 'text',
325
+ text: `āŒ Error: ${error instanceof Error ? error.message : 'Failed to generate password'}`,
326
+ },
327
+ ],
328
+ isError: true,
329
+ };
330
+ }
331
+ }
332
+ async function handleQRGen(args) {
333
+ const { text, size = 200, dark = '#000000', light = '#ffffff' } = args;
334
+ try {
335
+ if (!text) {
336
+ throw new Error('Text is required');
337
+ }
338
+ if (size < 100 || size > 1000) {
339
+ throw new Error('Size must be between 100 and 1000 pixels');
340
+ }
341
+ // Validate color format
342
+ const colorRegex = /^#[0-9A-Fa-f]{6}$/;
343
+ if (!colorRegex.test(dark) || !colorRegex.test(light)) {
344
+ throw new Error('Invalid color format. Use hexadecimal format (e.g., #000000)');
345
+ }
346
+ // Here we would normally generate the QR code
347
+ // For now, we'll return a placeholder message
348
+ return {
349
+ content: [
350
+ {
351
+ type: 'text',
352
+ text: `šŸ“± QR Code Properties:\n• Content: ${text}\n• Size: ${size}px\n• Dark Color: ${dark}\n• Light Color: ${light}\n\nšŸ”„ QR Code generation successful! (Implementation pending)`,
353
+ },
354
+ ],
355
+ isError: false,
356
+ };
357
+ }
358
+ catch (error) {
359
+ return {
360
+ content: [
361
+ {
362
+ type: 'text',
363
+ text: `āŒ Error: ${error instanceof Error ? error.message : 'Failed to generate QR code'}`,
364
+ },
365
+ ],
366
+ isError: true,
367
+ };
368
+ }
369
+ }
370
+ async function handleKitchenConvert(args) {
371
+ const { value, from, to, ingredient } = args;
372
+ // Conversion factors (base unit: milliliters for volume, grams for weight)
373
+ const volumeConversions = {
374
+ ml: 1, // milliliters
375
+ l: 1000, // liters
376
+ cup: 236.588, // US cup
377
+ tbsp: 14.787, // tablespoon
378
+ tsp: 4.929, // teaspoon
379
+ floz: 29.574, // fluid ounce
380
+ };
381
+ const weightConversions = {
382
+ g: 1, // grams
383
+ kg: 1000, // kilograms
384
+ oz: 28.3495, // ounces
385
+ lb: 453.592, // pounds
386
+ };
387
+ // Common ingredient densities (g/ml)
388
+ const densities = {
389
+ water: 1.0, // water density at room temperature
390
+ milk: 1.03, // whole milk
391
+ flour: 0.593, // all-purpose flour
392
+ sugar: 0.845, // granulated sugar
393
+ 'brown sugar': 0.721, // packed brown sugar
394
+ salt: 1.217, // table salt
395
+ butter: 0.911, // unsalted butter
396
+ oil: 0.918, // vegetable oil
397
+ honey: 1.42, // pure honey
398
+ 'maple syrup': 1.37, // pure maple syrup
399
+ };
400
+ try {
401
+ // Validate units
402
+ const fromUnit = from.toLowerCase();
403
+ const toUnit = to.toLowerCase();
404
+ const ing = ingredient?.toLowerCase();
405
+ // Check if units exist
406
+ if (!volumeConversions[fromUnit] && !weightConversions[fromUnit]) {
407
+ throw new Error(`Invalid source unit: ${from}`);
408
+ }
409
+ if (!volumeConversions[toUnit] && !weightConversions[toUnit]) {
410
+ throw new Error(`Invalid target unit: ${to}`);
411
+ }
412
+ let result;
413
+ // Same type conversion (volume to volume or weight to weight)
414
+ if ((volumeConversions[fromUnit] && volumeConversions[toUnit]) ||
415
+ (weightConversions[fromUnit] && weightConversions[toUnit])) {
416
+ const conversions = volumeConversions[fromUnit] ? volumeConversions : weightConversions;
417
+ result = (value * conversions[fromUnit]) / conversions[toUnit];
418
+ }
419
+ else {
420
+ // Volume to weight or weight to volume conversion
421
+ if (!ing || !densities[ing]) {
422
+ throw new Error(`Ingredient is required for volume-weight conversions. Available ingredients: ${Object.keys(densities).join(', ')}`);
423
+ }
424
+ // Convert to base units first (ml or g)
425
+ let baseValue;
426
+ if (volumeConversions[fromUnit]) {
427
+ baseValue = value * volumeConversions[fromUnit] * densities[ing];
428
+ result = baseValue / weightConversions[toUnit];
429
+ }
430
+ else {
431
+ baseValue = value * weightConversions[fromUnit];
432
+ result = baseValue / (volumeConversions[toUnit] * densities[ing]);
433
+ }
434
+ }
435
+ return {
436
+ content: [
437
+ {
438
+ type: 'text',
439
+ text: `šŸ”„ Conversion Result:\n• ${value} ${from} ${ingredient ? `of ${ingredient} ` : ''}= ${result.toFixed(2)} ${to}\n\nšŸ“ Note: ${ingredient ? 'Conversion includes ingredient density' : 'Direct unit conversion'}`,
440
+ },
441
+ ],
442
+ isError: false,
443
+ };
444
+ }
445
+ catch (error) {
446
+ return {
447
+ content: [
448
+ {
449
+ type: 'text',
450
+ text: `āŒ Error: ${error instanceof Error ? error.message : 'Invalid conversion'}`,
451
+ },
452
+ ],
453
+ isError: true,
454
+ };
455
+ }
456
+ }
457
+ // Tool call handler
110
458
  async function handleToolCall(name, args) {
111
459
  switch (name) {
112
460
  case 'greeting':
@@ -115,6 +463,14 @@ async function handleToolCall(name, args) {
115
463
  return handleCard();
116
464
  case 'datetime':
117
465
  return handleDateTime(args);
466
+ case 'calculator':
467
+ return handleCalculator(args);
468
+ case 'passwordGen':
469
+ return handlePasswordGen(args);
470
+ case 'qrGen':
471
+ return handleQRGen(args);
472
+ case 'kitchenConvert':
473
+ return handleKitchenConvert(args);
118
474
  default:
119
475
  return {
120
476
  content: [
@@ -127,8 +483,9 @@ async function handleToolCall(name, args) {
127
483
  };
128
484
  }
129
485
  }
486
+ // Server configuration
130
487
  const server = new Server({
131
- name: '@nekzus/server-nekzus',
488
+ name: '@nekzus/mcp-server',
132
489
  version: '0.1.0',
133
490
  description: 'MCP Server implementation for development',
134
491
  }, {
@@ -136,35 +493,40 @@ const server = new Server({
136
493
  tools: {},
137
494
  },
138
495
  });
496
+ // Setup request handlers
139
497
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
140
498
  tools: TOOLS,
141
499
  }));
142
500
  server.setRequestHandler(CallToolRequestSchema, async (request) => handleToolCall(request.params.name, request.params.arguments ?? {}));
501
+ // Server startup
143
502
  async function runServer() {
144
503
  try {
145
504
  const transport = new StdioServerTransport();
146
505
  await server.connect(transport);
147
- console.log('[Server] MCP Server is running');
148
- console.log('[Server] Available tools:', TOOLS.map((t) => t.name).join(', '));
506
+ log('[Server] MCP Server is running');
507
+ log('[Server] Available tools:', TOOLS.map((t) => t.name).join(', '));
508
+ // Handle stdin close
149
509
  process.stdin.on('close', () => {
150
- console.log('[Server] Input stream closed');
510
+ log('[Server] Input stream closed');
151
511
  cleanup();
152
512
  });
153
513
  }
154
514
  catch (error) {
155
- console.error('[Server] Failed to start MCP Server:', error);
515
+ log('[Server] Failed to start MCP Server:', error);
156
516
  process.exit(1);
157
517
  }
158
518
  }
519
+ // Cleanup function
159
520
  async function cleanup() {
160
521
  try {
161
522
  await server.close();
162
- console.log('[Server] MCP Server stopped gracefully');
523
+ log('[Server] MCP Server stopped gracefully');
163
524
  process.exit(0);
164
525
  }
165
526
  catch (error) {
166
- console.error('[Server] Error during cleanup:', error);
527
+ log('[Server] Error during cleanup:', error);
167
528
  process.exit(1);
168
529
  }
169
530
  }
170
- runServer().catch(console.error);
531
+ // Start the server
532
+ runServer().catch((error) => log('[Server] Unhandled error:', error));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nekzus/mcp-server",
3
- "version": "1.0.33",
3
+ "version": "1.0.35",
4
4
  "description": "Personal MCP Server implementation providing extensible utility functions and tools for development and testing purposes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -9,6 +9,7 @@
9
9
  },
10
10
  "files": [
11
11
  "dist",
12
+ "dist/index.js",
12
13
  "README.md",
13
14
  "LICENSE"
14
15
  ],