@nekzus/mcp-server 1.2.0 → 1.3.1

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/dist/index.js CHANGED
@@ -1,8 +1,96 @@
1
1
  #!/usr/bin/env node
2
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
- import QRCode from 'qrcode';
2
+ import fetch from 'node-fetch';
3
+ import { z } from 'zod';
4
+ // Zod schemas for npm package data
5
+ const NpmPackageVersionSchema = z
6
+ .object({
7
+ name: z.string(),
8
+ version: z.string(),
9
+ description: z.string().optional(),
10
+ author: z.union([z.string(), z.object({}).passthrough()]).optional(),
11
+ license: z.string().optional(),
12
+ repository: z
13
+ .object({
14
+ type: z.string().optional(),
15
+ url: z.string().optional(),
16
+ })
17
+ .passthrough()
18
+ .optional(),
19
+ bugs: z
20
+ .object({
21
+ url: z.string().optional(),
22
+ })
23
+ .passthrough()
24
+ .optional(),
25
+ homepage: z.string().optional(),
26
+ })
27
+ .passthrough();
28
+ const NpmPackageInfoSchema = z
29
+ .object({
30
+ name: z.string(),
31
+ 'dist-tags': z.record(z.string()),
32
+ versions: z.record(NpmPackageVersionSchema),
33
+ time: z.record(z.string()).optional(),
34
+ repository: z
35
+ .object({
36
+ type: z.string().optional(),
37
+ url: z.string().optional(),
38
+ })
39
+ .passthrough()
40
+ .optional(),
41
+ bugs: z
42
+ .object({
43
+ url: z.string().optional(),
44
+ })
45
+ .passthrough()
46
+ .optional(),
47
+ homepage: z.string().optional(),
48
+ })
49
+ .passthrough();
50
+ const NpmPackageDataSchema = z.object({
51
+ name: z.string(),
52
+ version: z.string(),
53
+ description: z.string().optional(),
54
+ license: z.string().optional(),
55
+ dependencies: z.record(z.string()).optional(),
56
+ devDependencies: z.record(z.string()).optional(),
57
+ peerDependencies: z.record(z.string()).optional(),
58
+ types: z.string().optional(),
59
+ typings: z.string().optional(),
60
+ });
61
+ const BundlephobiaDataSchema = z.object({
62
+ size: z.number(),
63
+ gzip: z.number(),
64
+ dependencyCount: z.number(),
65
+ });
66
+ const NpmDownloadsDataSchema = z.object({
67
+ downloads: z.number(),
68
+ start: z.string(),
69
+ end: z.string(),
70
+ package: z.string(),
71
+ });
72
+ // Schemas for NPM quality, maintenance and popularity metrics
73
+ const NpmQualitySchema = z.object({
74
+ score: z.number(),
75
+ tests: z.number(),
76
+ coverage: z.number(),
77
+ linting: z.number(),
78
+ types: z.number(),
79
+ });
80
+ const NpmMaintenanceSchema = z.object({
81
+ score: z.number(),
82
+ issuesResolutionTime: z.number(),
83
+ commitsFrequency: z.number(),
84
+ releaseFrequency: z.number(),
85
+ lastUpdate: z.string(),
86
+ });
87
+ const NpmPopularitySchema = z.object({
88
+ score: z.number(),
89
+ stars: z.number(),
90
+ downloads: z.number(),
91
+ dependents: z.number(),
92
+ communityInterest: z.number(),
93
+ });
6
94
  // Logger function that uses stderr - only for critical errors
7
95
  const log = (...args) => {
8
96
  // Filter out server status messages
@@ -14,198 +102,185 @@ const log = (...args) => {
14
102
  };
15
103
  // Define tools
16
104
  const TOOLS = [
105
+ // NPM Package Analysis Tools
17
106
  {
18
- name: 'greeting',
19
- description: 'Generate a personalized greeting message for the specified person',
107
+ name: 'npmVersions',
108
+ description: 'Get all available versions of an NPM package',
109
+ parameters: z.object({
110
+ packageName: z.string().describe('The name of the package'),
111
+ }),
20
112
  inputSchema: {
21
113
  type: 'object',
22
114
  properties: {
23
- name: {
24
- type: 'string',
25
- description: 'Name of the recipient for the greeting',
26
- },
115
+ packageName: { type: 'string' },
27
116
  },
28
- required: ['name'],
117
+ required: ['packageName'],
29
118
  },
30
119
  },
31
120
  {
32
- name: 'card',
33
- description: 'Draw a random card from a standard 52-card poker deck',
121
+ name: 'npmLatest',
122
+ description: 'Get the latest version and changelog of an NPM package',
123
+ parameters: z.object({
124
+ packageName: z.string().describe('The name of the package'),
125
+ }),
34
126
  inputSchema: {
35
127
  type: 'object',
36
- properties: {},
128
+ properties: {
129
+ packageName: { type: 'string' },
130
+ },
131
+ required: ['packageName'],
37
132
  },
38
133
  },
39
134
  {
40
- name: 'datetime',
41
- description: 'Get the current date and time for a specific timezone',
135
+ name: 'npmDeps',
136
+ description: 'Analyze dependencies and devDependencies of an NPM package',
137
+ parameters: z.object({
138
+ packageName: z.string().describe('The name of the package'),
139
+ }),
42
140
  inputSchema: {
43
141
  type: 'object',
44
142
  properties: {
45
- timeZone: {
46
- type: 'string',
47
- description: 'Timezone identifier (e.g., "America/New_York")',
48
- },
49
- locale: {
50
- type: 'string',
51
- description: 'Locale identifier (e.g., "en-US")',
52
- },
143
+ packageName: { type: 'string' },
53
144
  },
145
+ required: ['packageName'],
54
146
  },
55
147
  },
56
148
  {
57
- name: 'calculator',
58
- description: 'Perform mathematical calculations with support for basic and advanced operations',
149
+ name: 'npmTypes',
150
+ description: 'Check TypeScript types availability and version for a package',
151
+ parameters: z.object({
152
+ packageName: z.string().describe('The name of the package'),
153
+ }),
59
154
  inputSchema: {
60
155
  type: 'object',
61
156
  properties: {
62
- expression: {
63
- type: 'string',
64
- description: 'Mathematical expression to evaluate (e.g., "2 + 2 * 3")',
65
- },
66
- precision: {
67
- type: 'number',
68
- description: 'Number of decimal places for the result (default: 2)',
69
- },
157
+ packageName: { type: 'string' },
70
158
  },
71
- required: ['expression'],
159
+ required: ['packageName'],
72
160
  },
73
161
  },
74
162
  {
75
- name: 'passwordGen',
76
- description: 'Generate a secure password with customizable options',
163
+ name: 'npmSize',
164
+ description: 'Get package size information including dependencies and bundle size',
165
+ parameters: z.object({
166
+ packageName: z.string().describe('The name of the package'),
167
+ }),
77
168
  inputSchema: {
78
169
  type: 'object',
79
170
  properties: {
80
- length: {
81
- type: 'number',
82
- description: 'Length of the password (default: 16)',
83
- },
84
- includeNumbers: {
85
- type: 'boolean',
86
- description: 'Include numbers in the password (default: true)',
87
- },
88
- includeSymbols: {
89
- type: 'boolean',
90
- description: 'Include special symbols in the password (default: true)',
91
- },
92
- includeUppercase: {
93
- type: 'boolean',
94
- description: 'Include uppercase letters in the password (default: true)',
95
- },
171
+ packageName: { type: 'string' },
96
172
  },
173
+ required: ['packageName'],
97
174
  },
98
175
  },
99
176
  {
100
- name: 'qrGen',
101
- description: 'Generate a QR code for the given text or URL',
177
+ name: 'npmVulnerabilities',
178
+ description: 'Check for known vulnerabilities in a package',
179
+ parameters: z.object({
180
+ packageName: z.string().describe('The name of the package'),
181
+ }),
102
182
  inputSchema: {
103
183
  type: 'object',
104
184
  properties: {
105
- text: {
106
- type: 'string',
107
- description: 'Text or URL to encode in the QR code',
108
- },
109
- size: {
110
- type: 'number',
111
- description: 'Size of the QR code in pixels (default: 200)',
112
- },
113
- dark: {
114
- type: 'string',
115
- description: 'Color for dark modules (default: "#000000")',
116
- },
117
- light: {
118
- type: 'string',
119
- description: 'Color for light modules (default: "#ffffff")',
120
- },
185
+ packageName: { type: 'string' },
121
186
  },
122
- required: ['text'],
187
+ required: ['packageName'],
123
188
  },
124
189
  },
125
190
  {
126
- name: 'kitchenConvert',
127
- description: 'Convert between common kitchen measurements and weights',
191
+ name: 'npmTrends',
192
+ description: 'Get download trends and popularity metrics for a package',
193
+ parameters: z.object({
194
+ packageName: z.string().describe('The name of the package'),
195
+ period: z.enum(['last-week', 'last-month', 'last-year']).describe('Time period for trends'),
196
+ }),
128
197
  inputSchema: {
129
198
  type: 'object',
130
199
  properties: {
131
- value: {
132
- type: 'number',
133
- description: 'Value to convert',
134
- },
135
- from: {
136
- type: 'string',
137
- description: 'Source unit (e.g., "cup", "tbsp", "g", "oz", "ml")',
138
- },
139
- to: {
200
+ packageName: { type: 'string' },
201
+ period: {
140
202
  type: 'string',
141
- description: 'Target unit (e.g., "cup", "tbsp", "g", "oz", "ml")',
203
+ enum: ['last-week', 'last-month', 'last-year'],
142
204
  },
143
- ingredient: {
144
- type: 'string',
145
- description: 'Optional ingredient for accurate volume-to-weight conversions',
205
+ },
206
+ required: ['packageName', 'period'],
207
+ },
208
+ },
209
+ {
210
+ name: 'npmCompare',
211
+ description: 'Compare multiple NPM packages based on various metrics',
212
+ parameters: z.object({
213
+ packages: z.array(z.string()).describe('List of package names to compare'),
214
+ }),
215
+ inputSchema: {
216
+ type: 'object',
217
+ properties: {
218
+ packages: {
219
+ type: 'array',
220
+ items: { type: 'string' },
146
221
  },
147
222
  },
148
- required: ['value', 'from', 'to'],
223
+ required: ['packages'],
149
224
  },
150
225
  },
151
226
  ];
152
- // Tool handlers
153
- async function handleGreeting(args) {
154
- return {
155
- content: [
156
- {
157
- type: 'text',
158
- text: `👋 Hello ${args.name}! Welcome to the MCP server!`,
159
- },
160
- ],
161
- isError: false,
162
- };
227
+ // Type guards for API responses
228
+ function isNpmPackageInfo(data) {
229
+ try {
230
+ return NpmPackageInfoSchema.parse(data) !== null;
231
+ }
232
+ catch {
233
+ return false;
234
+ }
163
235
  }
164
- async function handleCard() {
165
- const suits = ['♠️', '♥️', '♣️', '♦️'];
166
- const values = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'];
167
- const suit = suits[Math.floor(Math.random() * suits.length)];
168
- const value = values[Math.floor(Math.random() * values.length)];
169
- return {
170
- content: [
171
- {
172
- type: 'text',
173
- text: `🎴 Drew card: ${value}${suit}`,
174
- },
175
- ],
176
- isError: false,
177
- };
236
+ function isNpmPackageData(data) {
237
+ try {
238
+ return NpmPackageDataSchema.parse(data) !== null;
239
+ }
240
+ catch {
241
+ return false;
242
+ }
178
243
  }
179
- async function handleDateTime(args) {
180
- const { timeZone = 'UTC', locale = 'en-US' } = args;
181
- const date = new Date();
182
- const formattedDate = new Intl.DateTimeFormat(locale, {
183
- timeZone,
184
- dateStyle: 'full',
185
- timeStyle: 'long',
186
- }).format(date);
187
- return {
188
- content: [
189
- {
190
- type: 'text',
191
- text: `🕒 Current date and time in ${timeZone}: ${formattedDate}`,
192
- },
193
- ],
194
- isError: false,
195
- };
244
+ function isBundlephobiaData(data) {
245
+ try {
246
+ return BundlephobiaDataSchema.parse(data) !== null;
247
+ }
248
+ catch {
249
+ return false;
250
+ }
196
251
  }
197
- async function handleCalculator(args) {
252
+ function isNpmDownloadsData(data) {
198
253
  try {
199
- const sanitizedExpression = args.expression.replace(/[^0-9+\-*/().%\s]/g, '');
200
- const calculate = new Function(`return ${sanitizedExpression}`);
201
- const result = calculate();
202
- const precision = args.precision ?? 2;
203
- const formattedResult = Number.isInteger(result) ? result : Number(result.toFixed(precision));
254
+ return NpmDownloadsDataSchema.parse(data) !== null;
255
+ }
256
+ catch {
257
+ return false;
258
+ }
259
+ }
260
+ async function handleNpmVersions(args) {
261
+ try {
262
+ const response = await fetch(`https://registry.npmjs.org/${args.packageName}`);
263
+ if (!response.ok) {
264
+ throw new Error(`Failed to fetch package info: ${response.statusText}`);
265
+ }
266
+ const rawData = await response.json();
267
+ if (!isNpmPackageInfo(rawData)) {
268
+ throw new Error('Invalid package info data received');
269
+ }
270
+ const versions = Object.keys(rawData.versions ?? {}).sort((a, b) => {
271
+ const [aMajor = 0, aMinor = 0, aPatch = 0] = a.split('.').map(Number);
272
+ const [bMajor = 0, bMinor = 0, bPatch = 0] = b.split('.').map(Number);
273
+ if (aMajor !== bMajor)
274
+ return aMajor - bMajor;
275
+ if (aMinor !== bMinor)
276
+ return aMinor - bMinor;
277
+ return aPatch - bPatch;
278
+ });
204
279
  return {
205
280
  content: [
206
281
  {
207
282
  type: 'text',
208
- text: `🔢 Result: ${formattedResult}`,
283
+ text: `📦 Available versions for ${args.packageName}:\n${versions.join('\n')}`,
209
284
  },
210
285
  ],
211
286
  isError: false,
@@ -216,56 +291,175 @@ async function handleCalculator(args) {
216
291
  content: [
217
292
  {
218
293
  type: 'text',
219
- text: `Error calculating result: ${error.message}`,
294
+ text: `Error fetching package versions: ${error instanceof Error ? error.message : 'Unknown error'}`,
220
295
  },
221
296
  ],
222
297
  isError: true,
223
298
  };
224
299
  }
225
300
  }
226
- async function handlePasswordGen(args) {
227
- const length = args.length ?? 16;
228
- const includeNumbers = args.includeNumbers ?? true;
229
- const includeSymbols = args.includeSymbols ?? true;
230
- const includeUppercase = args.includeUppercase ?? true;
231
- let chars = 'abcdefghijklmnopqrstuvwxyz';
232
- if (includeUppercase)
233
- chars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
234
- if (includeNumbers)
235
- chars += '0123456789';
236
- if (includeSymbols)
237
- chars += '!@#$%^&*()_+-=[]{}|;:,.<>?';
238
- let password = '';
239
- for (let i = 0; i < length; i++) {
240
- password += chars.charAt(Math.floor(Math.random() * chars.length));
301
+ async function handleNpmLatest(args) {
302
+ try {
303
+ const response = await fetch(`https://registry.npmjs.org/${args.packageName}`);
304
+ if (!response.ok) {
305
+ throw new Error(`Failed to fetch package info: ${response.statusText}`);
306
+ }
307
+ const rawData = await response.json();
308
+ if (!isNpmPackageInfo(rawData)) {
309
+ throw new Error('Invalid package info data received');
310
+ }
311
+ const latestVersion = rawData['dist-tags']?.latest;
312
+ if (!latestVersion || !rawData.versions) {
313
+ throw new Error('No latest version or versions data found');
314
+ }
315
+ const latestVersionInfo = rawData.versions[latestVersion];
316
+ const description = latestVersionInfo.description ?? '';
317
+ const repository = latestVersionInfo.repository ?? rawData.repository;
318
+ const homepage = latestVersionInfo.homepage ?? rawData.homepage;
319
+ const bugs = latestVersionInfo.bugs ?? rawData.bugs;
320
+ const text = [
321
+ `📦 Latest version of ${args.packageName}: ${latestVersion}`,
322
+ '',
323
+ description && `Description:\n${description}`,
324
+ '',
325
+ 'Links:',
326
+ homepage && `• Homepage: ${homepage}`,
327
+ repository?.url && `• Repository: ${repository.url.replace('git+', '').replace('.git', '')}`,
328
+ bugs?.url && `• Issues: ${bugs.url}`,
329
+ '',
330
+ repository?.url?.includes('github.com') &&
331
+ `You can check for updates at:\n${repository.url
332
+ .replace('git+', '')
333
+ .replace('git:', 'https:')
334
+ .replace('.git', '')}/releases`,
335
+ ]
336
+ .filter(Boolean)
337
+ .join('\n');
338
+ return {
339
+ content: [{ type: 'text', text }],
340
+ isError: false,
341
+ };
342
+ }
343
+ catch (error) {
344
+ return {
345
+ content: [
346
+ {
347
+ type: 'text',
348
+ text: `Error fetching package information: ${error instanceof Error ? error.message : 'Unknown error'}`,
349
+ },
350
+ ],
351
+ isError: true,
352
+ };
241
353
  }
242
- return {
243
- content: [
244
- {
245
- type: 'text',
246
- text: `🔐 Generated password: ${password}`,
247
- },
248
- ],
249
- isError: false,
250
- };
251
354
  }
252
- async function handleQRGen(args) {
355
+ async function handleNpmDeps(args) {
253
356
  try {
254
- const { text, size = 200, dark = '#000000', light = '#ffffff' } = args;
255
- // Generate QR code as Data URL
256
- const qrDataUrl = await QRCode.toDataURL(text, {
257
- width: size,
258
- margin: 1,
259
- color: {
260
- dark,
261
- light,
262
- },
263
- });
357
+ const response = await fetch(`https://registry.npmjs.org/${args.packageName}/latest`);
358
+ if (!response.ok) {
359
+ throw new Error(`Failed to fetch package info: ${response.statusText}`);
360
+ }
361
+ const rawData = await response.json();
362
+ if (!isNpmPackageData(rawData)) {
363
+ throw new Error('Invalid package data received');
364
+ }
365
+ const dependencies = rawData.dependencies ?? {};
366
+ const devDependencies = rawData.devDependencies ?? {};
367
+ const peerDependencies = rawData.peerDependencies ?? {};
368
+ const text = [
369
+ `📦 Dependencies for ${args.packageName}@${rawData.version}`,
370
+ '',
371
+ Object.keys(dependencies).length > 0 && [
372
+ 'Dependencies:',
373
+ ...Object.entries(dependencies).map(([dep, version]) => `• ${dep}: ${version}`),
374
+ '',
375
+ ],
376
+ Object.keys(devDependencies).length > 0 && [
377
+ 'Dev Dependencies:',
378
+ ...Object.entries(devDependencies).map(([dep, version]) => `• ${dep}: ${version}`),
379
+ '',
380
+ ],
381
+ Object.keys(peerDependencies).length > 0 && [
382
+ 'Peer Dependencies:',
383
+ ...Object.entries(peerDependencies).map(([dep, version]) => `• ${dep}: ${version}`),
384
+ ],
385
+ ]
386
+ .filter(Boolean)
387
+ .flat()
388
+ .join('\n');
389
+ return {
390
+ content: [{ type: 'text', text }],
391
+ isError: false,
392
+ };
393
+ }
394
+ catch (error) {
264
395
  return {
265
396
  content: [
266
397
  {
267
398
  type: 'text',
268
- text: `📱 QR Code generated successfully!\n\nProperties:\n• Content: ${text}\n• Size: ${size}px\n• Dark Color: ${dark}\n• Light Color: ${light}\n\nQR Code (Data URL):\n${qrDataUrl}`,
399
+ text: `Error fetching dependencies: ${error instanceof Error ? error.message : 'Unknown error'}`,
400
+ },
401
+ ],
402
+ isError: true,
403
+ };
404
+ }
405
+ }
406
+ async function handleNpmTypes(args) {
407
+ try {
408
+ const response = await fetch(`https://registry.npmjs.org/${args.packageName}/latest`);
409
+ if (!response.ok) {
410
+ throw new Error(`Failed to fetch package info: ${response.statusText}`);
411
+ }
412
+ const data = (await response.json());
413
+ let text = `📦 TypeScript support for ${args.packageName}@${data.version}\n\n`;
414
+ const hasTypes = Boolean(data.types || data.typings);
415
+ if (hasTypes) {
416
+ text += `✅ Package includes built-in TypeScript types\nTypes path: ${data.types || data.typings}\n\n`;
417
+ }
418
+ const typesPackage = `@types/${args.packageName.replace('@', '').replace('/', '__')}`;
419
+ const typesResponse = await fetch(`https://registry.npmjs.org/${typesPackage}/latest`).catch(() => null);
420
+ if (typesResponse?.ok) {
421
+ const typesData = (await typesResponse.json());
422
+ text += `📦 DefinitelyTyped package available: ${typesPackage}@${typesData.version}\n`;
423
+ text += `Install with: npm install -D ${typesPackage}\n`;
424
+ }
425
+ else if (!hasTypes) {
426
+ text += '❌ No TypeScript type definitions found\n';
427
+ }
428
+ return {
429
+ content: [{ type: 'text', text }],
430
+ isError: false,
431
+ };
432
+ }
433
+ catch (error) {
434
+ return {
435
+ content: [
436
+ { type: 'text', text: `Error checking TypeScript types: ${error.message}` },
437
+ ],
438
+ isError: true,
439
+ };
440
+ }
441
+ }
442
+ async function handleNpmSize(args) {
443
+ try {
444
+ const response = await fetch(`https://bundlephobia.com/api/size?package=${args.packageName}`);
445
+ if (!response.ok) {
446
+ throw new Error(`Failed to fetch package size: ${response.statusText}`);
447
+ }
448
+ const rawData = await response.json();
449
+ if (!isBundlephobiaData(rawData)) {
450
+ throw new Error('Invalid response from bundlephobia');
451
+ }
452
+ const sizeInKb = Number((rawData.size / 1024).toFixed(2));
453
+ const gzipInKb = Number((rawData.gzip / 1024).toFixed(2));
454
+ return {
455
+ content: [
456
+ {
457
+ type: 'text',
458
+ text: `Package size: ${sizeInKb}KB (gzipped: ${gzipInKb}KB)`,
459
+ },
460
+ {
461
+ type: 'text',
462
+ text: `Dependencies: ${rawData.dependencyCount}`,
269
463
  },
270
464
  ],
271
465
  isError: false,
@@ -276,104 +470,266 @@ async function handleQRGen(args) {
276
470
  content: [
277
471
  {
278
472
  type: 'text',
279
- text: `Error generating QR code: ${error.message}`,
473
+ text: `Error fetching package size: ${error instanceof Error ? error.message : String(error)}`,
280
474
  },
281
475
  ],
282
476
  isError: true,
283
477
  };
284
478
  }
285
479
  }
286
- async function handleKitchenConvert(args) {
287
- // Simplified conversion logic
288
- const result = args.value; // Add proper conversion logic here
289
- return {
290
- content: [
291
- {
292
- type: 'text',
293
- text: `⚖️ Converted ${args.value} ${args.from} to ${result} ${args.to}${args.ingredient ? ` of ${args.ingredient}` : ''}`,
480
+ async function handleNpmVulnerabilities(args) {
481
+ try {
482
+ const response = await fetch('https://api.osv.dev/v1/query', {
483
+ method: 'POST',
484
+ headers: {
485
+ 'Content-Type': 'application/json',
294
486
  },
295
- ],
296
- isError: false,
297
- };
487
+ body: JSON.stringify({
488
+ package: {
489
+ name: args.packageName,
490
+ ecosystem: 'npm',
491
+ },
492
+ }),
493
+ });
494
+ if (!response.ok) {
495
+ throw new Error(`Failed to fetch vulnerability info: ${response.statusText}`);
496
+ }
497
+ const data = (await response.json());
498
+ const vulns = data.vulns || [];
499
+ let text = `🔒 Security info for ${args.packageName}\n\n`;
500
+ if (vulns.length === 0) {
501
+ text += '✅ No known vulnerabilities\n';
502
+ }
503
+ else {
504
+ text += `⚠️ Found ${vulns.length} vulnerabilities:\n\n`;
505
+ for (const vuln of vulns) {
506
+ text += `- ${vuln.summary}\n`;
507
+ const severity = typeof vuln.severity === 'object'
508
+ ? vuln.severity.type || 'Unknown'
509
+ : vuln.severity || 'Unknown';
510
+ text += ` Severity: ${severity}\n`;
511
+ if (vuln.references && vuln.references.length > 0) {
512
+ text += ` More info: ${vuln.references[0].url}\n`;
513
+ }
514
+ text += '\n';
515
+ }
516
+ }
517
+ return {
518
+ content: [{ type: 'text', text }],
519
+ isError: false,
520
+ };
521
+ }
522
+ catch (error) {
523
+ return {
524
+ content: [
525
+ { type: 'text', text: `Error checking vulnerabilities: ${error.message}` },
526
+ ],
527
+ isError: true,
528
+ };
529
+ }
530
+ }
531
+ async function handleNpmTrends(args) {
532
+ try {
533
+ const period = args.period || 'last-month';
534
+ const response = await fetch(`https://api.npmjs.org/downloads/point/${period}/${args.packageName}`);
535
+ if (!response.ok) {
536
+ throw new Error(`Failed to fetch download trends: ${response.statusText}`);
537
+ }
538
+ const data = await response.json();
539
+ if (!isNpmDownloadsData(data)) {
540
+ throw new Error('Invalid response format from npm downloads API');
541
+ }
542
+ let text = `📈 Download trends for ${args.packageName}\n\n`;
543
+ text += `Period: ${period}\n`;
544
+ text += `Total downloads: ${data.downloads.toLocaleString()}\n`;
545
+ text += `Average daily downloads: ${Math.round(data.downloads / (period === 'last-week' ? 7 : period === 'last-month' ? 30 : 365)).toLocaleString()}\n`;
546
+ return {
547
+ content: [{ type: 'text', text }],
548
+ isError: false,
549
+ };
550
+ }
551
+ catch (error) {
552
+ return {
553
+ content: [
554
+ { type: 'text', text: `Error fetching download trends: ${error.message}` },
555
+ ],
556
+ isError: true,
557
+ };
558
+ }
298
559
  }
299
- async function handleToolCall(name, args) {
300
- switch (name) {
301
- case 'greeting':
302
- return handleGreeting(args);
303
- case 'card':
304
- return handleCard();
305
- case 'datetime':
306
- return handleDateTime(args);
307
- case 'calculator':
308
- return handleCalculator(args);
309
- case 'passwordGen':
310
- return handlePasswordGen(args);
311
- case 'qrGen':
312
- return handleQRGen(args);
313
- case 'kitchenConvert':
314
- return handleKitchenConvert(args);
315
- default:
560
+ async function handleNpmCompare(args) {
561
+ try {
562
+ const results = await Promise.all(args.packages.map(async (pkg) => {
563
+ const [infoRes, downloadsRes] = await Promise.all([
564
+ fetch(`https://registry.npmjs.org/${pkg}/latest`),
565
+ fetch(`https://api.npmjs.org/downloads/point/last-month/${pkg}`),
566
+ ]);
567
+ if (!infoRes.ok || !downloadsRes.ok) {
568
+ throw new Error(`Failed to fetch data for ${pkg}`);
569
+ }
570
+ const info = await infoRes.json();
571
+ const downloads = await downloadsRes.json();
572
+ if (!isNpmPackageData(info) || !isNpmDownloadsData(downloads)) {
573
+ throw new Error(`Invalid response format for ${pkg}`);
574
+ }
316
575
  return {
317
- content: [
318
- {
319
- type: 'text',
320
- text: `Unknown tool: ${name}`,
321
- },
322
- ],
323
- isError: true,
576
+ name: pkg,
577
+ version: info.version,
578
+ description: info.description,
579
+ downloads: downloads.downloads,
580
+ license: info.license,
581
+ dependencies: Object.keys(info.dependencies || {}).length,
324
582
  };
583
+ }));
584
+ let text = '📊 Package Comparison\n\n';
585
+ // Table header
586
+ text += 'Package | Version | Monthly Downloads | Dependencies | License\n';
587
+ text += '--------|---------|------------------|--------------|--------\n';
588
+ // Table rows
589
+ for (const pkg of results) {
590
+ text += `${pkg.name} | ${pkg.version} | ${pkg.downloads.toLocaleString()} | ${pkg.dependencies} | ${pkg.license || 'N/A'}\n`;
591
+ }
592
+ return {
593
+ content: [{ type: 'text', text }],
594
+ isError: false,
595
+ };
596
+ }
597
+ catch (error) {
598
+ return {
599
+ content: [{ type: 'text', text: `Error comparing packages: ${error.message}` }],
600
+ isError: true,
601
+ };
325
602
  }
326
603
  }
327
- const server = new Server({
328
- name: 'nekzus/mcp-server',
329
- version: '0.1.0',
330
- }, {
331
- capabilities: {
332
- resources: {},
333
- tools: {},
334
- },
335
- });
336
- // Setup request handlers
337
- server.setRequestHandler(ListResourcesRequestSchema, async () => ({
338
- resources: [],
339
- }));
340
- server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
341
- throw new Error(`Resource not found: ${request.params.uri}`);
342
- });
343
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
344
- tools: TOOLS,
345
- }));
346
- server.setRequestHandler(CallToolRequestSchema, async (request) => handleToolCall(request.params.name, request.params.arguments ?? {}));
347
- async function runServer() {
348
- const transport = new StdioServerTransport();
349
- // Handle direct messages
350
- process.stdin.on('data', async (data) => {
351
- try {
352
- const message = JSON.parse(data.toString());
353
- if (message.method === 'tools/call') {
354
- const result = await handleToolCall(message.params.name, message.params.arguments ?? {});
355
- process.stdout.write(`${JSON.stringify({
356
- jsonrpc: '2.0',
357
- result,
358
- id: message.id,
359
- })}\n`);
360
- }
604
+ // Function to get package quality metrics
605
+ async function handleNpmQuality(args) {
606
+ try {
607
+ const response = await fetch(`https://api.npms.io/v2/package/${encodeURIComponent(args.packageName)}`);
608
+ if (!response.ok) {
609
+ throw new Error(`Failed to fetch quality data: ${response.statusText}`);
361
610
  }
362
- catch (error) {
363
- if (error instanceof Error) {
364
- process.stdout.write(`${JSON.stringify({
365
- jsonrpc: '2.0',
366
- error: {
367
- code: -32000,
368
- message: error.message,
369
- },
370
- })}\n`);
371
- }
611
+ const data = (await response.json());
612
+ const quality = data.score.quality;
613
+ const result = NpmQualitySchema.parse({
614
+ score: Math.round(quality.score * 100) / 100,
615
+ tests: Math.round(quality.tests * 100) / 100,
616
+ coverage: Math.round(quality.coverage * 100) / 100,
617
+ linting: Math.round(quality.linting * 100) / 100,
618
+ types: Math.round(quality.types * 100) / 100,
619
+ });
620
+ return {
621
+ content: [
622
+ {
623
+ type: 'text',
624
+ text: `Quality metrics for ${args.packageName}:
625
+ - Overall Score: ${result.score}
626
+ - Tests: ${result.tests}
627
+ - Coverage: ${result.coverage}
628
+ - Linting: ${result.linting}
629
+ - Types: ${result.types}`,
630
+ },
631
+ ],
632
+ isError: false,
633
+ };
634
+ }
635
+ catch (error) {
636
+ return {
637
+ content: [
638
+ {
639
+ type: 'text',
640
+ text: `Error fetching quality metrics: ${error instanceof Error ? error.message : 'Unknown error'}`,
641
+ },
642
+ ],
643
+ isError: true,
644
+ };
645
+ }
646
+ }
647
+ // Function to get package maintenance metrics
648
+ async function handleNpmMaintenance(args) {
649
+ try {
650
+ const response = await fetch(`https://api.npms.io/v2/package/${encodeURIComponent(args.packageName)}`);
651
+ if (!response.ok) {
652
+ throw new Error(`Failed to fetch maintenance data: ${response.statusText}`);
372
653
  }
373
- });
374
- await server.connect(transport);
654
+ const data = (await response.json());
655
+ const maintenance = data.score.maintenance;
656
+ const result = NpmMaintenanceSchema.parse({
657
+ score: Math.round(maintenance.score * 100) / 100,
658
+ issuesResolutionTime: Math.round(maintenance.issuesResolutionTime * 100) / 100,
659
+ commitsFrequency: Math.round(maintenance.commitsFrequency * 100) / 100,
660
+ releaseFrequency: Math.round(maintenance.releaseFrequency * 100) / 100,
661
+ lastUpdate: new Date(maintenance.lastUpdate).toISOString(),
662
+ });
663
+ return {
664
+ content: [
665
+ {
666
+ type: 'text',
667
+ text: `Maintenance metrics for ${args.packageName}:
668
+ - Overall Score: ${result.score}
669
+ - Issues Resolution Time: ${result.issuesResolutionTime}
670
+ - Commits Frequency: ${result.commitsFrequency}
671
+ - Release Frequency: ${result.releaseFrequency}
672
+ - Last Update: ${new Date(result.lastUpdate).toLocaleDateString()}`,
673
+ },
674
+ ],
675
+ isError: false,
676
+ };
677
+ }
678
+ catch (error) {
679
+ return {
680
+ content: [
681
+ {
682
+ type: 'text',
683
+ text: `Error fetching maintenance metrics: ${error instanceof Error ? error.message : 'Unknown error'}`,
684
+ },
685
+ ],
686
+ isError: true,
687
+ };
688
+ }
375
689
  }
376
- runServer().catch(log);
377
- process.stdin.on('close', () => {
378
- server.close();
379
- });
690
+ // Function to get package popularity metrics
691
+ async function handleNpmPopularity(args) {
692
+ try {
693
+ const response = await fetch(`https://api.npms.io/v2/package/${encodeURIComponent(args.packageName)}`);
694
+ if (!response.ok) {
695
+ throw new Error(`Failed to fetch popularity data: ${response.statusText}`);
696
+ }
697
+ const data = (await response.json());
698
+ const popularity = data.score.popularity;
699
+ const result = NpmPopularitySchema.parse({
700
+ score: Math.round(popularity.score * 100) / 100,
701
+ stars: Math.round(popularity.stars),
702
+ downloads: Math.round(popularity.downloads),
703
+ dependents: Math.round(popularity.dependents),
704
+ communityInterest: Math.round(popularity.communityInterest * 100) / 100,
705
+ });
706
+ return {
707
+ content: [
708
+ {
709
+ type: 'text',
710
+ text: `Popularity metrics for ${args.packageName}:
711
+ - Overall Score: ${result.score}
712
+ - GitHub Stars: ${result.stars}
713
+ - Downloads: ${result.downloads}
714
+ - Dependent Packages: ${result.dependents}
715
+ - Community Interest: ${result.communityInterest}`,
716
+ },
717
+ ],
718
+ isError: false,
719
+ };
720
+ }
721
+ catch (error) {
722
+ return {
723
+ content: [
724
+ {
725
+ type: 'text',
726
+ text: `Error fetching popularity metrics: ${error instanceof Error ? error.message : 'Unknown error'}`,
727
+ },
728
+ ],
729
+ isError: true,
730
+ };
731
+ }
732
+ }
733
+ // Export functions
734
+ export { handleNpmCompare, handleNpmDeps, handleNpmLatest, handleNpmMaintenance, handleNpmPopularity, handleNpmQuality, handleNpmSize, handleNpmTrends, handleNpmTypes, handleNpmVersions, handleNpmVulnerabilities, };
735
+ //# sourceMappingURL=index.js.map