@nekzus/mcp-server 1.4.2 → 1.5.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
@@ -4,6 +4,13 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
4
4
  import fetch from 'node-fetch';
5
5
  import { z } from 'zod';
6
6
  // Zod schemas for npm package data
7
+ export const NpmMaintainerSchema = z
8
+ .object({
9
+ name: z.string(),
10
+ email: z.string().optional(),
11
+ url: z.string().optional(),
12
+ })
13
+ .passthrough();
7
14
  export const NpmPackageVersionSchema = z
8
15
  .object({
9
16
  name: z.string(),
@@ -47,6 +54,7 @@ export const NpmPackageInfoSchema = z
47
54
  .passthrough()
48
55
  .optional(),
49
56
  homepage: z.string().optional(),
57
+ maintainers: z.array(NpmMaintainerSchema).optional(),
50
58
  })
51
59
  .passthrough();
52
60
  export const NpmPackageDataSchema = z.object({
@@ -93,6 +101,98 @@ export const NpmPopularitySchema = z.object({
93
101
  dependents: z.number(),
94
102
  communityInterest: z.number(),
95
103
  });
104
+ function isValidNpmsResponse(data) {
105
+ if (typeof data !== 'object' || data === null) {
106
+ console.debug('Response is not an object or is null');
107
+ return false;
108
+ }
109
+ const response = data;
110
+ // Check score structure
111
+ if (!response.score ||
112
+ typeof response.score !== 'object' ||
113
+ !('final' in response.score) ||
114
+ typeof response.score.final !== 'number' ||
115
+ !('detail' in response.score) ||
116
+ typeof response.score.detail !== 'object') {
117
+ console.debug('Invalid score structure');
118
+ return false;
119
+ }
120
+ // Check score detail metrics
121
+ const detail = response.score.detail;
122
+ if (typeof detail.quality !== 'number' ||
123
+ typeof detail.popularity !== 'number' ||
124
+ typeof detail.maintenance !== 'number') {
125
+ console.debug('Invalid score detail metrics');
126
+ return false;
127
+ }
128
+ // Check collected data structure
129
+ if (!response.collected ||
130
+ typeof response.collected !== 'object' ||
131
+ !response.collected.metadata ||
132
+ typeof response.collected.metadata !== 'object' ||
133
+ typeof response.collected.metadata.name !== 'string' ||
134
+ typeof response.collected.metadata.version !== 'string') {
135
+ console.debug('Invalid collected data structure');
136
+ return false;
137
+ }
138
+ // Check npm data
139
+ if (!response.collected.npm ||
140
+ typeof response.collected.npm !== 'object' ||
141
+ !Array.isArray(response.collected.npm.downloads) ||
142
+ typeof response.collected.npm.starsCount !== 'number') {
143
+ console.debug('Invalid npm data structure');
144
+ return false;
145
+ }
146
+ // Optional github data check
147
+ if (response.collected.github) {
148
+ if (typeof response.collected.github !== 'object' ||
149
+ typeof response.collected.github.starsCount !== 'number' ||
150
+ typeof response.collected.github.forksCount !== 'number' ||
151
+ typeof response.collected.github.subscribersCount !== 'number' ||
152
+ !response.collected.github.issues ||
153
+ typeof response.collected.github.issues !== 'object' ||
154
+ typeof response.collected.github.issues.count !== 'number' ||
155
+ typeof response.collected.github.issues.openCount !== 'number') {
156
+ console.debug('Invalid github data structure');
157
+ return false;
158
+ }
159
+ }
160
+ return true;
161
+ }
162
+ export const NpmSearchResultSchema = z
163
+ .object({
164
+ objects: z.array(z.object({
165
+ package: z.object({
166
+ name: z.string(),
167
+ version: z.string(),
168
+ description: z.string().optional(),
169
+ keywords: z.array(z.string()).optional(),
170
+ publisher: z
171
+ .object({
172
+ username: z.string(),
173
+ })
174
+ .optional(),
175
+ links: z
176
+ .object({
177
+ npm: z.string().optional(),
178
+ homepage: z.string().optional(),
179
+ repository: z.string().optional(),
180
+ })
181
+ .optional(),
182
+ }),
183
+ score: z.object({
184
+ final: z.number(),
185
+ detail: z.object({
186
+ quality: z.number(),
187
+ popularity: z.number(),
188
+ maintenance: z.number(),
189
+ }),
190
+ }),
191
+ searchScore: z.number(),
192
+ })),
193
+ total: z.number(),
194
+ })
195
+ .passthrough();
96
196
  // Logger function that uses stderr - only for critical errors
97
197
  const log = (...args) => {
98
198
  // Filter out server status messages
@@ -108,104 +208,152 @@ const TOOLS = [
108
208
  {
109
209
  name: 'npmVersions',
110
210
  description: 'Get all available versions of an NPM package',
111
- parameters: z.object({
112
- packageName: z.string().describe('The name of the package'),
113
- }),
211
+ parameters: z.union([
212
+ z.object({
213
+ packageName: z.string().describe('The name of the package'),
214
+ }),
215
+ z.object({
216
+ packages: z.array(z.string()).describe('List of package names to get versions for'),
217
+ }),
218
+ ]),
114
219
  inputSchema: {
115
220
  type: 'object',
116
221
  properties: {
117
222
  packageName: { type: 'string' },
223
+ packages: { type: 'array', items: { type: 'string' } },
118
224
  },
119
- required: ['packageName'],
225
+ oneOf: [{ required: ['packageName'] }, { required: ['packages'] }],
120
226
  },
121
227
  },
122
228
  {
123
229
  name: 'npmLatest',
124
230
  description: 'Get the latest version and changelog of an NPM package',
125
- parameters: z.object({
126
- packageName: z.string().describe('The name of the package'),
127
- }),
231
+ parameters: z.union([
232
+ z.object({
233
+ packageName: z.string().describe('The name of the package'),
234
+ }),
235
+ z.object({
236
+ packages: z.array(z.string()).describe('List of package names to get latest versions for'),
237
+ }),
238
+ ]),
128
239
  inputSchema: {
129
240
  type: 'object',
130
241
  properties: {
131
242
  packageName: { type: 'string' },
243
+ packages: { type: 'array', items: { type: 'string' } },
132
244
  },
133
- required: ['packageName'],
245
+ oneOf: [{ required: ['packageName'] }, { required: ['packages'] }],
134
246
  },
135
247
  },
136
248
  {
137
249
  name: 'npmDeps',
138
250
  description: 'Analyze dependencies and devDependencies of an NPM package',
139
- parameters: z.object({
140
- packageName: z.string().describe('The name of the package'),
141
- }),
251
+ parameters: z.union([
252
+ z.object({
253
+ packageName: z.string().describe('The name of the package'),
254
+ }),
255
+ z.object({
256
+ packages: z.array(z.string()).describe('List of package names to analyze dependencies for'),
257
+ }),
258
+ ]),
142
259
  inputSchema: {
143
260
  type: 'object',
144
261
  properties: {
145
262
  packageName: { type: 'string' },
263
+ packages: { type: 'array', items: { type: 'string' } },
146
264
  },
147
- required: ['packageName'],
265
+ oneOf: [{ required: ['packageName'] }, { required: ['packages'] }],
148
266
  },
149
267
  },
150
268
  {
151
269
  name: 'npmTypes',
152
270
  description: 'Check TypeScript types availability and version for a package',
153
- parameters: z.object({
154
- packageName: z.string().describe('The name of the package'),
155
- }),
271
+ parameters: z.union([
272
+ z.object({
273
+ packageName: z.string().describe('The name of the package'),
274
+ }),
275
+ z.object({
276
+ packages: z.array(z.string()).describe('List of package names to check types for'),
277
+ }),
278
+ ]),
156
279
  inputSchema: {
157
280
  type: 'object',
158
281
  properties: {
159
282
  packageName: { type: 'string' },
283
+ packages: { type: 'array', items: { type: 'string' } },
160
284
  },
161
- required: ['packageName'],
285
+ oneOf: [{ required: ['packageName'] }, { required: ['packages'] }],
162
286
  },
163
287
  },
164
288
  {
165
289
  name: 'npmSize',
166
290
  description: 'Get package size information including dependencies and bundle size',
167
- parameters: z.object({
168
- packageName: z.string().describe('The name of the package'),
169
- }),
291
+ parameters: z.union([
292
+ z.object({
293
+ packageName: z.string().describe('The name of the package'),
294
+ }),
295
+ z.object({
296
+ packages: z.array(z.string()).describe('List of package names to get size information for'),
297
+ }),
298
+ ]),
170
299
  inputSchema: {
171
300
  type: 'object',
172
301
  properties: {
173
302
  packageName: { type: 'string' },
303
+ packages: { type: 'array', items: { type: 'string' } },
174
304
  },
175
- required: ['packageName'],
305
+ oneOf: [{ required: ['packageName'] }, { required: ['packages'] }],
176
306
  },
177
307
  },
178
308
  {
179
309
  name: 'npmVulnerabilities',
180
- description: 'Check for known vulnerabilities in a package',
181
- parameters: z.object({
182
- packageName: z.string().describe('The name of the package'),
183
- }),
310
+ description: 'Check for known vulnerabilities in packages',
311
+ parameters: z.union([
312
+ z.object({
313
+ packageName: z.string().describe('The name of the package'),
314
+ }),
315
+ z.object({
316
+ packages: z
317
+ .array(z.string())
318
+ .describe('List of package names to check for vulnerabilities'),
319
+ }),
320
+ ]),
184
321
  inputSchema: {
185
322
  type: 'object',
186
323
  properties: {
187
324
  packageName: { type: 'string' },
325
+ packages: { type: 'array', items: { type: 'string' } },
188
326
  },
189
- required: ['packageName'],
327
+ oneOf: [{ required: ['packageName'] }, { required: ['packages'] }],
190
328
  },
191
329
  },
192
330
  {
193
331
  name: 'npmTrends',
194
- description: 'Get download trends and popularity metrics for a package',
332
+ description: 'Get download trends and popularity metrics for packages. Available periods: "last-week" (7 days), "last-month" (30 days), or "last-year" (365 days)',
195
333
  parameters: z.object({
196
- packageName: z.string().describe('The name of the package'),
197
- period: z.enum(['last-week', 'last-month', 'last-year']).describe('Time period for trends'),
334
+ packages: z.array(z.string()).describe('List of package names to get trends for'),
335
+ period: z
336
+ .enum(['last-week', 'last-month', 'last-year'])
337
+ .describe('Time period for trends. Options: "last-week", "last-month", "last-year"')
338
+ .optional()
339
+ .default('last-month'),
198
340
  }),
199
341
  inputSchema: {
200
342
  type: 'object',
201
343
  properties: {
202
- packageName: { type: 'string' },
344
+ packages: {
345
+ type: 'array',
346
+ items: { type: 'string' },
347
+ description: 'List of package names to get trends for',
348
+ },
203
349
  period: {
204
350
  type: 'string',
205
351
  enum: ['last-week', 'last-month', 'last-year'],
352
+ description: 'Time period for trends. Options: "last-week" (7 days), "last-month" (30 days), or "last-year" (365 days)',
353
+ default: 'last-month',
206
354
  },
207
355
  },
208
- required: ['packageName', 'period'],
356
+ required: ['packages'],
209
357
  },
210
358
  },
211
359
  {
@@ -225,15 +373,95 @@ const TOOLS = [
225
373
  required: ['packages'],
226
374
  },
227
375
  },
376
+ {
377
+ name: 'npmMaintainers',
378
+ description: 'Get maintainers for an NPM package',
379
+ parameters: z.object({
380
+ packageName: z.string().describe('The name of the package'),
381
+ }),
382
+ inputSchema: {
383
+ type: 'object',
384
+ properties: {
385
+ packageName: { type: 'string' },
386
+ },
387
+ required: ['packageName'],
388
+ },
389
+ },
390
+ {
391
+ name: 'npmScore',
392
+ description: 'Get consolidated package score based on quality, maintenance, and popularity metrics',
393
+ parameters: z.union([
394
+ z.object({
395
+ packageName: z.string().describe('The name of the package'),
396
+ }),
397
+ z.object({
398
+ packages: z.array(z.string()).describe('List of package names to get scores for'),
399
+ }),
400
+ ]),
401
+ inputSchema: {
402
+ type: 'object',
403
+ properties: {
404
+ packageName: { type: 'string' },
405
+ packages: { type: 'array', items: { type: 'string' } },
406
+ },
407
+ oneOf: [{ required: ['packageName'] }, { required: ['packages'] }],
408
+ },
409
+ },
410
+ {
411
+ name: 'npmPackageReadme',
412
+ description: 'Get the README for an NPM package',
413
+ parameters: z.union([
414
+ z.object({
415
+ packageName: z.string().describe('The name of the package'),
416
+ }),
417
+ z.object({
418
+ packages: z.array(z.string()).describe('List of package names to get READMEs for'),
419
+ }),
420
+ ]),
421
+ inputSchema: {
422
+ type: 'object',
423
+ properties: {
424
+ packageName: { type: 'string' },
425
+ packages: { type: 'array', items: { type: 'string' } },
426
+ },
427
+ oneOf: [{ required: ['packageName'] }, { required: ['packages'] }],
428
+ },
429
+ },
430
+ {
431
+ name: 'npmSearch',
432
+ description: 'Search for NPM packages',
433
+ parameters: z.object({
434
+ query: z.string().describe('Search query for packages'),
435
+ limit: z
436
+ .number()
437
+ .min(1)
438
+ .max(50)
439
+ .optional()
440
+ .describe('Maximum number of results to return (default: 10)'),
441
+ }),
442
+ inputSchema: {
443
+ type: 'object',
444
+ properties: {
445
+ query: { type: 'string' },
446
+ limit: { type: 'number', minimum: 1, maximum: 50 },
447
+ },
448
+ required: ['query'],
449
+ },
450
+ },
228
451
  ];
229
452
  // Type guards for API responses
230
453
  function isNpmPackageInfo(data) {
231
- try {
232
- return NpmPackageInfoSchema.parse(data) !== null;
233
- }
234
- catch {
235
- return false;
236
- }
454
+ return (typeof data === 'object' &&
455
+ data !== null &&
456
+ (!('maintainers' in data) ||
457
+ (Array.isArray(data.maintainers) &&
458
+ (data.maintainers?.every((m) => typeof m === 'object' &&
459
+ m !== null &&
460
+ 'name' in m &&
461
+ 'email' in m &&
462
+ typeof m.name === 'string' &&
463
+ typeof m.email === 'string') ??
464
+ true))));
237
465
  }
238
466
  function isNpmPackageData(data) {
239
467
  try {
@@ -261,30 +489,41 @@ function isNpmDownloadsData(data) {
261
489
  }
262
490
  async function handleNpmVersions(args) {
263
491
  try {
264
- const response = await fetch(`https://registry.npmjs.org/${args.packageName}`);
265
- if (!response.ok) {
266
- throw new Error(`Failed to fetch package info: ${response.statusText}`);
492
+ const packagesToProcess = args.packages || [];
493
+ if (packagesToProcess.length === 0) {
494
+ throw new Error('No package names provided');
495
+ }
496
+ const results = await Promise.all(packagesToProcess.map(async (pkg) => {
497
+ const response = await fetch(`https://registry.npmjs.org/${pkg}`);
498
+ if (!response.ok) {
499
+ return { name: pkg, error: `Failed to fetch package info: ${response.statusText}` };
500
+ }
501
+ const rawData = await response.json();
502
+ if (!isNpmPackageInfo(rawData)) {
503
+ return { name: pkg, error: 'Invalid package info data received' };
504
+ }
505
+ const versions = Object.keys(rawData.versions ?? {}).sort((a, b) => {
506
+ const [aMajor = 0, aMinor = 0, aPatch = 0] = a.split('.').map(Number);
507
+ const [bMajor = 0, bMinor = 0, bPatch = 0] = b.split('.').map(Number);
508
+ if (aMajor !== bMajor)
509
+ return aMajor - bMajor;
510
+ if (aMinor !== bMinor)
511
+ return aMinor - bMinor;
512
+ return aPatch - bPatch;
513
+ });
514
+ return { name: pkg, versions };
515
+ }));
516
+ let text = '';
517
+ for (const result of results) {
518
+ if ('error' in result) {
519
+ text += `❌ ${result.name}: ${result.error}\n\n`;
520
+ }
521
+ else {
522
+ text += `📦 Available versions for ${result.name}:\n${result.versions.join('\n')}\n\n`;
523
+ }
267
524
  }
268
- const rawData = await response.json();
269
- if (!isNpmPackageInfo(rawData)) {
270
- throw new Error('Invalid package info data received');
271
- }
272
- const versions = Object.keys(rawData.versions ?? {}).sort((a, b) => {
273
- const [aMajor = 0, aMinor = 0, aPatch = 0] = a.split('.').map(Number);
274
- const [bMajor = 0, bMinor = 0, bPatch = 0] = b.split('.').map(Number);
275
- if (aMajor !== bMajor)
276
- return aMajor - bMajor;
277
- if (aMinor !== bMinor)
278
- return aMinor - bMinor;
279
- return aPatch - bPatch;
280
- });
281
525
  return {
282
- content: [
283
- {
284
- type: 'text',
285
- text: `📦 Available versions for ${args.packageName}:\n${versions.join('\n')}`,
286
- },
287
- ],
526
+ content: [{ type: 'text', text }],
288
527
  isError: false,
289
528
  };
290
529
  }
@@ -302,44 +541,29 @@ async function handleNpmVersions(args) {
302
541
  }
303
542
  async function handleNpmLatest(args) {
304
543
  try {
305
- // Fetch full package info instead of just latest
306
- const response = await fetch(`https://registry.npmjs.org/${args.packageName}`);
307
- if (!response.ok) {
308
- throw new Error(`Failed to fetch package info: ${response.statusText}`);
544
+ const packages = args.packages || [];
545
+ let text = '';
546
+ for (const pkg of packages) {
547
+ const response = await fetch(`https://registry.npmjs.org/${pkg}/latest`);
548
+ if (!response.ok) {
549
+ throw new Error(`Failed to fetch latest version for ${pkg}: ${response.statusText}`);
550
+ }
551
+ const data = (await response.json());
552
+ text += `📦 Latest version of ${pkg}\n`;
553
+ text += `Version: ${data.version}\n`;
554
+ text += `Description: ${data.description || 'No description available'}\n`;
555
+ text += `Author: ${data.author?.name || 'Unknown'}\n`;
556
+ text += `License: ${data.license || 'Unknown'}\n`;
557
+ text += `Homepage: ${data.homepage || 'Not specified'}\n\n`;
558
+ text += '---\n\n';
309
559
  }
310
- const rawData = await response.json();
311
- if (!isNpmPackageInfo(rawData)) {
312
- throw new Error('Invalid package info data received');
313
- }
314
- const latestVersion = rawData['dist-tags']?.latest;
315
- if (!latestVersion || !rawData.versions?.[latestVersion]) {
316
- throw new Error('No latest version found');
317
- }
318
- const latestVersionInfo = rawData.versions[latestVersion];
319
- const description = latestVersionInfo.description ?? '';
320
- const repository = latestVersionInfo.repository ?? rawData.repository;
321
- const homepage = latestVersionInfo.homepage ?? rawData.homepage;
322
- const bugs = latestVersionInfo.bugs ?? rawData.bugs;
323
- const text = [
324
- `📦 Latest version of ${args.packageName}: ${latestVersion}`,
325
- '',
326
- description && `Description:\n${description}`,
327
- '',
328
- 'Links:',
329
- homepage && `• Homepage: ${homepage}`,
330
- repository?.url && `• Repository: ${repository.url.replace('git+', '').replace('.git', '')}`,
331
- bugs?.url && `• Issues: ${bugs.url}`,
332
- '',
333
- repository?.url?.includes('github.com') &&
334
- `You can check for updates at:\n${repository.url
335
- .replace('git+', '')
336
- .replace('git:', 'https:')
337
- .replace('.git', '')}/releases`,
338
- ]
339
- .filter(Boolean)
340
- .join('\n');
341
560
  return {
342
- content: [{ type: 'text', text }],
561
+ content: [
562
+ {
563
+ type: 'text',
564
+ text,
565
+ },
566
+ ],
343
567
  isError: false,
344
568
  };
345
569
  }
@@ -348,7 +572,7 @@ async function handleNpmLatest(args) {
348
572
  content: [
349
573
  {
350
574
  type: 'text',
351
- text: `Error fetching package information: ${error instanceof Error ? error.message : 'Unknown error'}`,
575
+ text: `Error fetching latest version: ${error instanceof Error ? error.message : 'Unknown error'}`,
352
576
  },
353
577
  ],
354
578
  isError: true,
@@ -357,38 +581,62 @@ async function handleNpmLatest(args) {
357
581
  }
358
582
  async function handleNpmDeps(args) {
359
583
  try {
360
- const response = await fetch(`https://registry.npmjs.org/${args.packageName}/latest`);
361
- if (!response.ok) {
362
- throw new Error(`Failed to fetch package info: ${response.statusText}`);
584
+ const packagesToProcess = args.packages || [];
585
+ if (packagesToProcess.length === 0) {
586
+ throw new Error('No package names provided');
587
+ }
588
+ const results = await Promise.all(packagesToProcess.map(async (pkg) => {
589
+ try {
590
+ const response = await fetch(`https://registry.npmjs.org/${pkg}/latest`);
591
+ if (!response.ok) {
592
+ return { name: pkg, error: `Failed to fetch package info: ${response.statusText}` };
593
+ }
594
+ const rawData = await response.json();
595
+ if (!isNpmPackageData(rawData)) {
596
+ return { name: pkg, error: 'Invalid package data received' };
597
+ }
598
+ return {
599
+ name: pkg,
600
+ version: rawData.version,
601
+ dependencies: rawData.dependencies ?? {},
602
+ devDependencies: rawData.devDependencies ?? {},
603
+ peerDependencies: rawData.peerDependencies ?? {},
604
+ };
605
+ }
606
+ catch (error) {
607
+ return { name: pkg, error: error instanceof Error ? error.message : 'Unknown error' };
608
+ }
609
+ }));
610
+ let text = '';
611
+ for (const result of results) {
612
+ if ('error' in result) {
613
+ text += `❌ ${result.name}: ${result.error}\n\n`;
614
+ continue;
615
+ }
616
+ text += `📦 Dependencies for ${result.name}@${result.version}\n\n`;
617
+ if (Object.keys(result.dependencies).length > 0) {
618
+ text += 'Dependencies:\n';
619
+ for (const [dep, version] of Object.entries(result.dependencies)) {
620
+ text += `• ${dep}: ${version}\n`;
621
+ }
622
+ text += '\n';
623
+ }
624
+ if (Object.keys(result.devDependencies).length > 0) {
625
+ text += 'Dev Dependencies:\n';
626
+ for (const [dep, version] of Object.entries(result.devDependencies)) {
627
+ text += `• ${dep}: ${version}\n`;
628
+ }
629
+ text += '\n';
630
+ }
631
+ if (Object.keys(result.peerDependencies).length > 0) {
632
+ text += 'Peer Dependencies:\n';
633
+ for (const [dep, version] of Object.entries(result.peerDependencies)) {
634
+ text += `• ${dep}: ${version}\n`;
635
+ }
636
+ text += '\n';
637
+ }
638
+ text += '---\n\n';
363
639
  }
364
- const rawData = await response.json();
365
- if (!isNpmPackageData(rawData)) {
366
- throw new Error('Invalid package data received');
367
- }
368
- const dependencies = rawData.dependencies ?? {};
369
- const devDependencies = rawData.devDependencies ?? {};
370
- const peerDependencies = rawData.peerDependencies ?? {};
371
- const text = [
372
- `📦 Dependencies for ${args.packageName}@${rawData.version}`,
373
- '',
374
- Object.keys(dependencies).length > 0 && [
375
- 'Dependencies:',
376
- ...Object.entries(dependencies).map(([dep, version]) => `• ${dep}: ${version}`),
377
- '',
378
- ],
379
- Object.keys(devDependencies).length > 0 && [
380
- 'Dev Dependencies:',
381
- ...Object.entries(devDependencies).map(([dep, version]) => `• ${dep}: ${version}`),
382
- '',
383
- ],
384
- Object.keys(peerDependencies).length > 0 && [
385
- 'Peer Dependencies:',
386
- ...Object.entries(peerDependencies).map(([dep, version]) => `• ${dep}: ${version}`),
387
- ],
388
- ]
389
- .filter(Boolean)
390
- .flat()
391
- .join('\n');
392
640
  return {
393
641
  content: [{ type: 'text', text }],
394
642
  isError: false,
@@ -408,25 +656,36 @@ async function handleNpmDeps(args) {
408
656
  }
409
657
  async function handleNpmTypes(args) {
410
658
  try {
411
- const response = await fetch(`https://registry.npmjs.org/${args.packageName}/latest`);
412
- if (!response.ok) {
413
- throw new Error(`Failed to fetch package info: ${response.statusText}`);
414
- }
415
- const data = (await response.json());
416
- let text = `📦 TypeScript support for ${args.packageName}@${data.version}\n\n`;
417
- const hasTypes = Boolean(data.types || data.typings);
418
- if (hasTypes) {
419
- text += `✅ Package includes built-in TypeScript types\nTypes path: ${data.types || data.typings}\n\n`;
420
- }
421
- const typesPackage = `@types/${args.packageName.replace('@', '').replace('/', '__')}`;
422
- const typesResponse = await fetch(`https://registry.npmjs.org/${typesPackage}/latest`).catch(() => null);
423
- if (typesResponse?.ok) {
424
- const typesData = (await typesResponse.json());
425
- text += `📦 DefinitelyTyped package available: ${typesPackage}@${typesData.version}\n`;
426
- text += `Install with: npm install -D ${typesPackage}\n`;
427
- }
428
- else if (!hasTypes) {
429
- text += '❌ No TypeScript type definitions found\n';
659
+ const results = await Promise.all(args.packages.map(async (pkg) => {
660
+ const response = await fetch(`https://registry.npmjs.org/${pkg}/latest`);
661
+ if (!response.ok) {
662
+ throw new Error(`Failed to fetch package info: ${response.statusText}`);
663
+ }
664
+ const data = (await response.json());
665
+ let text = `📦 TypeScript support for ${pkg}@${data.version}\n`;
666
+ const hasTypes = Boolean(data.types || data.typings);
667
+ if (hasTypes) {
668
+ text += '✅ Package includes built-in TypeScript types\n';
669
+ text += `Types path: ${data.types || data.typings}\n`;
670
+ }
671
+ const typesPackage = `@types/${pkg.replace('@', '').replace('/', '__')}`;
672
+ const typesResponse = await fetch(`https://registry.npmjs.org/${typesPackage}/latest`).catch(() => null);
673
+ if (typesResponse?.ok) {
674
+ const typesData = (await typesResponse.json());
675
+ text += `📦 DefinitelyTyped package available: ${typesPackage}@${typesData.version}\n`;
676
+ text += `Install with: npm install -D ${typesPackage}`;
677
+ }
678
+ else if (!hasTypes) {
679
+ text += '❌ No TypeScript type definitions found';
680
+ }
681
+ return { name: pkg, text };
682
+ }));
683
+ let text = '';
684
+ for (const result of results) {
685
+ text += `${result.text}\n\n`;
686
+ if (results.indexOf(result) < results.length - 1) {
687
+ text += '---\n\n';
688
+ }
430
689
  }
431
690
  return {
432
691
  content: [{ type: 'text', text }],
@@ -444,27 +703,39 @@ async function handleNpmTypes(args) {
444
703
  }
445
704
  async function handleNpmSize(args) {
446
705
  try {
447
- const response = await fetch(`https://bundlephobia.com/api/size?package=${args.packageName}`);
448
- if (!response.ok) {
449
- throw new Error(`Failed to fetch package size: ${response.statusText}`);
706
+ const packagesToProcess = args.packages || [];
707
+ if (packagesToProcess.length === 0) {
708
+ throw new Error('No package names provided');
450
709
  }
451
- const rawData = await response.json();
452
- if (!isBundlephobiaData(rawData)) {
453
- throw new Error('Invalid response from bundlephobia');
710
+ const results = await Promise.all(packagesToProcess.map(async (pkg) => {
711
+ const response = await fetch(`https://bundlephobia.com/api/size?package=${pkg}`);
712
+ if (!response.ok) {
713
+ return { name: pkg, error: `Failed to fetch package size: ${response.statusText}` };
714
+ }
715
+ const rawData = await response.json();
716
+ if (!isBundlephobiaData(rawData)) {
717
+ return { name: pkg, error: 'Invalid response from bundlephobia' };
718
+ }
719
+ return {
720
+ name: pkg,
721
+ sizeInKb: Number((rawData.size / 1024).toFixed(2)),
722
+ gzipInKb: Number((rawData.gzip / 1024).toFixed(2)),
723
+ dependencyCount: rawData.dependencyCount,
724
+ };
725
+ }));
726
+ let text = '';
727
+ for (const result of results) {
728
+ if ('error' in result) {
729
+ text += `❌ ${result.name}: ${result.error}\n\n`;
730
+ }
731
+ else {
732
+ text += `📦 ${result.name}\n`;
733
+ text += `Size: ${result.sizeInKb}KB (gzipped: ${result.gzipInKb}KB)\n`;
734
+ text += `Dependencies: ${result.dependencyCount}\n\n`;
735
+ }
454
736
  }
455
- const sizeInKb = Number((rawData.size / 1024).toFixed(2));
456
- const gzipInKb = Number((rawData.gzip / 1024).toFixed(2));
457
737
  return {
458
- content: [
459
- {
460
- type: 'text',
461
- text: `Package size: ${sizeInKb}KB (gzipped: ${gzipInKb}KB)`,
462
- },
463
- {
464
- type: 'text',
465
- text: `Dependencies: ${rawData.dependencyCount}`,
466
- },
467
- ],
738
+ content: [{ type: 'text', text }],
468
739
  isError: false,
469
740
  };
470
741
  }
@@ -473,7 +744,7 @@ async function handleNpmSize(args) {
473
744
  content: [
474
745
  {
475
746
  type: 'text',
476
- text: `Error fetching package size: ${error instanceof Error ? error.message : String(error)}`,
747
+ text: `Error fetching package sizes: ${error instanceof Error ? error.message : 'Unknown error'}`,
477
748
  },
478
749
  ],
479
750
  isError: true,
@@ -482,40 +753,54 @@ async function handleNpmSize(args) {
482
753
  }
483
754
  async function handleNpmVulnerabilities(args) {
484
755
  try {
485
- const response = await fetch('https://api.osv.dev/v1/query', {
486
- method: 'POST',
487
- headers: {
488
- 'Content-Type': 'application/json',
489
- },
490
- body: JSON.stringify({
491
- package: {
492
- name: args.packageName,
493
- ecosystem: 'npm',
756
+ const packagesToProcess = args.packages || [];
757
+ if (packagesToProcess.length === 0) {
758
+ throw new Error('No package names provided');
759
+ }
760
+ const results = await Promise.all(packagesToProcess.map(async (pkg) => {
761
+ const response = await fetch('https://api.osv.dev/v1/query', {
762
+ method: 'POST',
763
+ headers: {
764
+ 'Content-Type': 'application/json',
494
765
  },
495
- }),
496
- });
497
- if (!response.ok) {
498
- throw new Error(`Failed to fetch vulnerability info: ${response.statusText}`);
499
- }
500
- const data = (await response.json());
501
- const vulns = data.vulns || [];
502
- let text = `🔒 Security info for ${args.packageName}\n\n`;
503
- if (vulns.length === 0) {
504
- text += '✅ No known vulnerabilities\n';
505
- }
506
- else {
507
- text += `⚠️ Found ${vulns.length} vulnerabilities:\n\n`;
508
- for (const vuln of vulns) {
509
- text += `- ${vuln.summary}\n`;
510
- const severity = typeof vuln.severity === 'object'
511
- ? vuln.severity.type || 'Unknown'
512
- : vuln.severity || 'Unknown';
513
- text += ` Severity: ${severity}\n`;
514
- if (vuln.references && vuln.references.length > 0) {
515
- text += ` More info: ${vuln.references[0].url}\n`;
766
+ body: JSON.stringify({
767
+ package: {
768
+ name: pkg,
769
+ ecosystem: 'npm',
770
+ },
771
+ }),
772
+ });
773
+ if (!response.ok) {
774
+ return { name: pkg, error: `Failed to fetch vulnerability info: ${response.statusText}` };
775
+ }
776
+ const data = (await response.json());
777
+ return { name: pkg, vulns: data.vulns || [] };
778
+ }));
779
+ let text = '🔒 Security Analysis\n\n';
780
+ for (const result of results) {
781
+ if ('error' in result) {
782
+ text += `❌ ${result.name}: ${result.error}\n\n`;
783
+ continue;
784
+ }
785
+ text += `📦 ${result.name}\n`;
786
+ if (result.vulns.length === 0) {
787
+ text += '✅ No known vulnerabilities\n\n';
788
+ }
789
+ else {
790
+ text += `⚠️ Found ${result.vulns.length} vulnerabilities:\n\n`;
791
+ for (const vuln of result.vulns) {
792
+ text += `- ${vuln.summary}\n`;
793
+ const severity = typeof vuln.severity === 'object'
794
+ ? vuln.severity.type || 'Unknown'
795
+ : vuln.severity || 'Unknown';
796
+ text += ` Severity: ${severity}\n`;
797
+ if (vuln.references && vuln.references.length > 0) {
798
+ text += ` More info: ${vuln.references[0].url}\n`;
799
+ }
800
+ text += '\n';
516
801
  }
517
- text += '\n';
518
802
  }
803
+ text += '---\n\n';
519
804
  }
520
805
  return {
521
806
  content: [{ type: 'text', text }],
@@ -525,7 +810,10 @@ async function handleNpmVulnerabilities(args) {
525
810
  catch (error) {
526
811
  return {
527
812
  content: [
528
- { type: 'text', text: `Error checking vulnerabilities: ${error.message}` },
813
+ {
814
+ type: 'text',
815
+ text: `Error checking vulnerabilities: ${error instanceof Error ? error.message : 'Unknown error'}`,
816
+ },
529
817
  ],
530
818
  isError: true,
531
819
  };
@@ -534,18 +822,55 @@ async function handleNpmVulnerabilities(args) {
534
822
  async function handleNpmTrends(args) {
535
823
  try {
536
824
  const period = args.period || 'last-month';
537
- const response = await fetch(`https://api.npmjs.org/downloads/point/${period}/${args.packageName}`);
538
- if (!response.ok) {
539
- throw new Error(`Failed to fetch download trends: ${response.statusText}`);
540
- }
541
- const data = await response.json();
542
- if (!isNpmDownloadsData(data)) {
543
- throw new Error('Invalid response format from npm downloads API');
825
+ const periodDays = {
826
+ 'last-week': 7,
827
+ 'last-month': 30,
828
+ 'last-year': 365,
829
+ };
830
+ const results = await Promise.all(args.packages.map(async (pkg) => {
831
+ const response = await fetch(`https://api.npmjs.org/downloads/point/${period}/${pkg}`);
832
+ if (!response.ok) {
833
+ return {
834
+ name: pkg,
835
+ error: `Failed to fetch download trends: ${response.statusText}`,
836
+ success: false,
837
+ };
838
+ }
839
+ const data = await response.json();
840
+ if (!isNpmDownloadsData(data)) {
841
+ return {
842
+ name: pkg,
843
+ error: 'Invalid response format from npm downloads API',
844
+ success: false,
845
+ };
846
+ }
847
+ return {
848
+ name: pkg,
849
+ downloads: data.downloads,
850
+ success: true,
851
+ };
852
+ }));
853
+ let text = '📈 Download Trends\n\n';
854
+ text += `Period: ${period} (${periodDays[period]} days)\n\n`;
855
+ // Individual package stats
856
+ for (const result of results) {
857
+ if (!result.success) {
858
+ text += `❌ ${result.name}: ${result.error}\n`;
859
+ continue;
860
+ }
861
+ text += `📦 ${result.name}\n`;
862
+ text += `Total downloads: ${result.downloads.toLocaleString()}\n`;
863
+ text += `Average daily downloads: ${Math.round(result.downloads / periodDays[period]).toLocaleString()}\n\n`;
544
864
  }
545
- let text = `📈 Download trends for ${args.packageName}\n\n`;
546
- text += `Period: ${period}\n`;
547
- text += `Total downloads: ${data.downloads.toLocaleString()}\n`;
548
- text += `Average daily downloads: ${Math.round(data.downloads / (period === 'last-week' ? 7 : period === 'last-month' ? 30 : 365)).toLocaleString()}\n`;
865
+ // Total stats
866
+ const totalDownloads = results.reduce((total, result) => {
867
+ if (result.success) {
868
+ return total + result.downloads;
869
+ }
870
+ return total;
871
+ }, 0);
872
+ text += `Total downloads across all packages: ${totalDownloads.toLocaleString()}\n`;
873
+ text += `Average daily downloads across all packages: ${Math.round(totalDownloads / periodDays[period]).toLocaleString()}\n`;
549
874
  return {
550
875
  content: [{ type: 'text', text }],
551
876
  isError: false,
@@ -607,31 +932,83 @@ async function handleNpmCompare(args) {
607
932
  // Function to get package quality metrics
608
933
  async function handleNpmQuality(args) {
609
934
  try {
610
- const response = await fetch(`https://api.npms.io/v2/package/${encodeURIComponent(args.packageName)}`);
611
- if (!response.ok) {
612
- throw new Error(`Failed to fetch quality data: ${response.statusText}`);
613
- }
614
- const data = (await response.json());
615
- const quality = data.score.quality;
616
- const result = NpmQualitySchema.parse({
617
- score: Math.round(quality.score * 100) / 100,
618
- tests: Math.round(quality.tests * 100) / 100,
619
- coverage: Math.round(quality.coverage * 100) / 100,
620
- linting: Math.round(quality.linting * 100) / 100,
621
- types: Math.round(quality.types * 100) / 100,
622
- });
935
+ const results = await Promise.all(args.packages.map(async (pkg) => {
936
+ const response = await fetch(`https://api.npms.io/v2/package/${encodeURIComponent(pkg)}`);
937
+ if (!response.ok) {
938
+ return { name: pkg, error: `Failed to fetch quality data: ${response.statusText}` };
939
+ }
940
+ const rawData = await response.json();
941
+ if (!isValidNpmsResponse(rawData)) {
942
+ return { name: pkg, error: 'Invalid response format from npms.io API' };
943
+ }
944
+ const quality = rawData.score.detail.quality;
945
+ return {
946
+ name: pkg,
947
+ ...NpmQualitySchema.parse({
948
+ score: Math.round(quality * 100) / 100,
949
+ tests: 0, // These values are no longer available in the API
950
+ coverage: 0,
951
+ linting: 0,
952
+ types: 0,
953
+ }),
954
+ };
955
+ }));
956
+ let text = '📊 Quality Metrics\n\n';
957
+ for (const result of results) {
958
+ if ('error' in result) {
959
+ text += `❌ ${result.name}: ${result.error}\n\n`;
960
+ continue;
961
+ }
962
+ text += `📦 ${result.name}\n`;
963
+ text += `- Overall Score: ${result.score}\n`;
964
+ text +=
965
+ '- Note: Detailed metrics (tests, coverage, linting, types) are no longer provided by the API\n\n';
966
+ }
967
+ return {
968
+ content: [{ type: 'text', text }],
969
+ isError: false,
970
+ };
971
+ }
972
+ catch (error) {
623
973
  return {
624
974
  content: [
625
975
  {
626
976
  type: 'text',
627
- text: `Quality metrics for ${args.packageName}:
628
- - Overall Score: ${result.score}
629
- - Tests: ${result.tests}
630
- - Coverage: ${result.coverage}
631
- - Linting: ${result.linting}
632
- - Types: ${result.types}`,
977
+ text: `Error fetching quality metrics: ${error instanceof Error ? error.message : 'Unknown error'}`,
633
978
  },
634
979
  ],
980
+ isError: true,
981
+ };
982
+ }
983
+ }
984
+ async function handleNpmMaintenance(args) {
985
+ try {
986
+ const results = await Promise.all(args.packages.map(async (pkg) => {
987
+ const response = await fetch(`https://api.npms.io/v2/package/${encodeURIComponent(pkg)}`);
988
+ if (!response.ok) {
989
+ return { name: pkg, error: `Failed to fetch maintenance data: ${response.statusText}` };
990
+ }
991
+ const rawData = await response.json();
992
+ if (!isValidNpmsResponse(rawData)) {
993
+ return { name: pkg, error: 'Invalid response format from npms.io API' };
994
+ }
995
+ const maintenance = rawData.score.detail.maintenance;
996
+ return {
997
+ name: pkg,
998
+ score: Math.round(maintenance * 100) / 100,
999
+ };
1000
+ }));
1001
+ let text = '🛠️ Maintenance Metrics\n\n';
1002
+ for (const result of results) {
1003
+ if ('error' in result) {
1004
+ text += `❌ ${result.name}: ${result.error}\n\n`;
1005
+ continue;
1006
+ }
1007
+ text += `📦 ${result.name}\n`;
1008
+ text += `- Maintenance Score: ${result.score}\n\n`;
1009
+ }
1010
+ return {
1011
+ content: [{ type: 'text', text }],
635
1012
  isError: false,
636
1013
  };
637
1014
  }
@@ -640,39 +1017,113 @@ async function handleNpmQuality(args) {
640
1017
  content: [
641
1018
  {
642
1019
  type: 'text',
643
- text: `Error fetching quality metrics: ${error instanceof Error ? error.message : 'Unknown error'}`,
1020
+ text: `Error fetching maintenance metrics: ${error instanceof Error ? error.message : 'Unknown error'}`,
644
1021
  },
645
1022
  ],
646
1023
  isError: true,
647
1024
  };
648
1025
  }
649
1026
  }
650
- // Function to get package maintenance metrics
651
- async function handleNpmMaintenance(args) {
1027
+ async function handleNpmPopularity(args) {
652
1028
  try {
653
- const response = await fetch(`https://api.npms.io/v2/package/${encodeURIComponent(args.packageName)}`);
654
- if (!response.ok) {
655
- throw new Error(`Failed to fetch maintenance data: ${response.statusText}`);
656
- }
657
- const data = (await response.json());
658
- const maintenance = data.score.maintenance;
659
- const result = NpmMaintenanceSchema.parse({
660
- score: Math.round(maintenance.score * 100) / 100,
661
- issuesResolutionTime: Math.round(maintenance.issuesResolutionTime * 100) / 100,
662
- commitsFrequency: Math.round(maintenance.commitsFrequency * 100) / 100,
663
- releaseFrequency: Math.round(maintenance.releaseFrequency * 100) / 100,
664
- lastUpdate: new Date(maintenance.lastUpdate).toISOString(),
665
- });
1029
+ const results = await Promise.all(args.packages.map(async (pkg) => {
1030
+ const response = await fetch(`https://api.npms.io/v2/package/${encodeURIComponent(pkg)}`);
1031
+ if (!response.ok) {
1032
+ return { name: pkg, error: `Failed to fetch popularity data: ${response.statusText}` };
1033
+ }
1034
+ const data = await response.json();
1035
+ if (!isValidNpmsResponse(data)) {
1036
+ return { name: pkg, error: 'Invalid API response format' };
1037
+ }
1038
+ const popularityScore = data.score.detail.popularity;
1039
+ return {
1040
+ name: pkg,
1041
+ ...NpmPopularitySchema.parse({
1042
+ score: Math.round(popularityScore * 100) / 100,
1043
+ stars: 0,
1044
+ downloads: 0,
1045
+ dependents: 0,
1046
+ communityInterest: 0,
1047
+ }),
1048
+ };
1049
+ }));
1050
+ let text = '📈 Popularity Metrics\n\n';
1051
+ for (const result of results) {
1052
+ if ('error' in result) {
1053
+ text += `❌ ${result.name}: ${result.error}\n\n`;
1054
+ continue;
1055
+ }
1056
+ text += `📦 ${result.name}\n`;
1057
+ text += `- Overall Score: ${result.score}\n`;
1058
+ text += '- Note: Detailed metrics are no longer provided by the API\n\n';
1059
+ }
1060
+ return {
1061
+ content: [{ type: 'text', text }],
1062
+ isError: false,
1063
+ };
1064
+ }
1065
+ catch (error) {
1066
+ return {
1067
+ content: [
1068
+ {
1069
+ type: 'text',
1070
+ text: `Error fetching popularity metrics: ${error instanceof Error ? error.message : 'Unknown error'}`,
1071
+ },
1072
+ ],
1073
+ isError: true,
1074
+ };
1075
+ }
1076
+ }
1077
+ async function handleNpmMaintainers(args) {
1078
+ try {
1079
+ const results = await Promise.all(args.packages.map(async (pkg) => {
1080
+ const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(pkg)}`);
1081
+ if (response.status === 404) {
1082
+ return {
1083
+ name: pkg,
1084
+ error: 'Package not found in the npm registry',
1085
+ };
1086
+ }
1087
+ if (!response.ok) {
1088
+ throw new Error(`API request failed with status ${response.status} (${response.statusText})`);
1089
+ }
1090
+ const data = await response.json();
1091
+ if (!isNpmPackageInfo(data)) {
1092
+ throw new Error('Invalid package info data received');
1093
+ }
1094
+ return {
1095
+ name: pkg,
1096
+ maintainers: data.maintainers || [],
1097
+ };
1098
+ }));
1099
+ let text = '👥 Package Maintainers\n\n';
1100
+ for (const result of results) {
1101
+ if ('error' in result) {
1102
+ text += `❌ ${result.name}: ${result.error}\n\n`;
1103
+ continue;
1104
+ }
1105
+ text += `📦 ${result.name}\n`;
1106
+ text += `${'-'.repeat(40)}\n`;
1107
+ const maintainers = result.maintainers || [];
1108
+ if (maintainers.length === 0) {
1109
+ text += '⚠️ No maintainers found.\n';
1110
+ }
1111
+ else {
1112
+ text += `👥 Maintainers (${maintainers.length}):\n\n`;
1113
+ for (const maintainer of maintainers) {
1114
+ text += `• ${maintainer.name}\n`;
1115
+ text += ` 📧 ${maintainer.email}\n\n`;
1116
+ }
1117
+ }
1118
+ if (results.indexOf(result) < results.length - 1) {
1119
+ text += '\n';
1120
+ }
1121
+ }
666
1122
  return {
667
1123
  content: [
668
1124
  {
669
1125
  type: 'text',
670
- text: `Maintenance metrics for ${args.packageName}:
671
- - Overall Score: ${result.score}
672
- - Issues Resolution Time: ${result.issuesResolutionTime}
673
- - Commits Frequency: ${result.commitsFrequency}
674
- - Release Frequency: ${result.releaseFrequency}
675
- - Last Update: ${new Date(result.lastUpdate).toLocaleDateString()}`,
1126
+ text,
676
1127
  },
677
1128
  ],
678
1129
  isError: false,
@@ -683,41 +1134,331 @@ async function handleNpmMaintenance(args) {
683
1134
  content: [
684
1135
  {
685
1136
  type: 'text',
686
- text: `Error fetching maintenance metrics: ${error instanceof Error ? error.message : 'Unknown error'}`,
1137
+ text: `Error fetching package maintainers: ${error instanceof Error ? error.message : 'Unknown error'}`,
687
1138
  },
688
1139
  ],
689
1140
  isError: true,
690
1141
  };
691
1142
  }
692
1143
  }
693
- // Function to get package popularity metrics
694
- async function handleNpmPopularity(args) {
1144
+ async function handleNpmScore(args) {
1145
+ try {
1146
+ const results = await Promise.all(args.packages.map(async (pkg) => {
1147
+ const response = await fetch(`https://api.npms.io/v2/package/${encodeURIComponent(pkg)}`);
1148
+ if (response.status === 404) {
1149
+ return {
1150
+ name: pkg,
1151
+ error: 'Package not found in the npm registry',
1152
+ };
1153
+ }
1154
+ if (!response.ok) {
1155
+ throw new Error(`API request failed with status ${response.status} (${response.statusText})`);
1156
+ }
1157
+ const rawData = await response.json();
1158
+ if (!isValidNpmsResponse(rawData)) {
1159
+ return {
1160
+ name: pkg,
1161
+ error: 'Invalid or incomplete response from npms.io API',
1162
+ };
1163
+ }
1164
+ const { score, collected } = rawData;
1165
+ const { detail } = score;
1166
+ return {
1167
+ name: pkg,
1168
+ score,
1169
+ detail,
1170
+ collected,
1171
+ };
1172
+ }));
1173
+ let text = '📊 Package Scores\n\n';
1174
+ for (const result of results) {
1175
+ if ('error' in result) {
1176
+ text += `❌ ${result.name}: ${result.error}\n\n`;
1177
+ continue;
1178
+ }
1179
+ text += `📦 ${result.name}\n`;
1180
+ text += `${'-'.repeat(40)}\n`;
1181
+ text += `Overall Score: ${(result.score.final * 100).toFixed(1)}%\n\n`;
1182
+ text += '🎯 Quality Breakdown:\n';
1183
+ text += `• Quality: ${(result.detail.quality * 100).toFixed(1)}%\n`;
1184
+ text += `• Maintenance: ${(result.detail.maintenance * 100).toFixed(1)}%\n`;
1185
+ text += `• Popularity: ${(result.detail.popularity * 100).toFixed(1)}%\n\n`;
1186
+ if (result.collected.github) {
1187
+ text += '📈 GitHub Stats:\n';
1188
+ text += `• Stars: ${result.collected.github.starsCount.toLocaleString()}\n`;
1189
+ text += `• Forks: ${result.collected.github.forksCount.toLocaleString()}\n`;
1190
+ text += `• Watchers: ${result.collected.github.subscribersCount.toLocaleString()}\n`;
1191
+ text += `• Total Issues: ${result.collected.github.issues.count.toLocaleString()}\n`;
1192
+ text += `• Open Issues: ${result.collected.github.issues.openCount.toLocaleString()}\n\n`;
1193
+ }
1194
+ if (result.collected.npm?.downloads?.length > 0) {
1195
+ const lastDownloads = result.collected.npm.downloads[0];
1196
+ text += '📥 NPM Downloads:\n';
1197
+ text += `• Last day: ${lastDownloads.count.toLocaleString()} (${new Date(lastDownloads.from).toLocaleDateString()} - ${new Date(lastDownloads.to).toLocaleDateString()})\n\n`;
1198
+ }
1199
+ if (results.indexOf(result) < results.length - 1) {
1200
+ text += '\n';
1201
+ }
1202
+ }
1203
+ // Retornar en el formato MCP estándar
1204
+ return {
1205
+ content: [
1206
+ {
1207
+ type: 'text',
1208
+ text,
1209
+ },
1210
+ ],
1211
+ isError: false,
1212
+ };
1213
+ }
1214
+ catch (error) {
1215
+ // Manejo de errores en formato MCP estándar
1216
+ return {
1217
+ content: [
1218
+ {
1219
+ type: 'text',
1220
+ text: `Error fetching package scores: ${error instanceof Error ? error.message : 'Unknown error'}`,
1221
+ },
1222
+ ],
1223
+ isError: true,
1224
+ };
1225
+ }
1226
+ }
1227
+ async function handleNpmPackageReadme(args) {
1228
+ try {
1229
+ const results = await Promise.all(args.packages.map(async (pkg) => {
1230
+ const response = await fetch(`https://registry.npmjs.org/${pkg}`);
1231
+ if (!response.ok) {
1232
+ throw new Error(`Failed to fetch package info: ${response.statusText}`);
1233
+ }
1234
+ const rawData = await response.json();
1235
+ if (!isNpmPackageInfo(rawData)) {
1236
+ throw new Error('Invalid package info data received');
1237
+ }
1238
+ const latestVersion = rawData['dist-tags']?.latest;
1239
+ if (!latestVersion || !rawData.versions?.[latestVersion]) {
1240
+ throw new Error('No latest version found');
1241
+ }
1242
+ const readme = rawData.versions[latestVersion].readme || rawData.readme;
1243
+ if (!readme) {
1244
+ return { name: pkg, version: latestVersion, text: 'No README found' };
1245
+ }
1246
+ return { name: pkg, version: latestVersion, text: readme };
1247
+ }));
1248
+ let text = '';
1249
+ for (const result of results) {
1250
+ text += `${'='.repeat(80)}\n`;
1251
+ text += `📖 ${result.name}@${result.version}\n`;
1252
+ text += `${'='.repeat(80)}\n\n`;
1253
+ text += result.text;
1254
+ if (results.indexOf(result) < results.length - 1) {
1255
+ text += '\n\n';
1256
+ text += `${'='.repeat(80)}\n\n`;
1257
+ }
1258
+ }
1259
+ return {
1260
+ content: [{ type: 'text', text }],
1261
+ isError: false,
1262
+ };
1263
+ }
1264
+ catch (error) {
1265
+ return {
1266
+ content: [
1267
+ {
1268
+ type: 'text',
1269
+ text: `Error fetching READMEs: ${error instanceof Error ? error.message : 'Unknown error'}`,
1270
+ },
1271
+ ],
1272
+ isError: true,
1273
+ };
1274
+ }
1275
+ }
1276
+ async function handleNpmSearch(args) {
695
1277
  try {
696
- const response = await fetch(`https://api.npms.io/v2/package/${encodeURIComponent(args.packageName)}`);
1278
+ const limit = args.limit || 10;
1279
+ const response = await fetch(`https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(args.query)}&size=${limit}`);
697
1280
  if (!response.ok) {
698
- throw new Error(`Failed to fetch popularity data: ${response.statusText}`);
699
- }
700
- const data = (await response.json());
701
- const popularity = data.score.popularity;
702
- const result = NpmPopularitySchema.parse({
703
- score: Math.round(popularity.score * 100) / 100,
704
- stars: Math.round(popularity.stars),
705
- downloads: Math.round(popularity.downloads),
706
- dependents: Math.round(popularity.dependents),
707
- communityInterest: Math.round(popularity.communityInterest * 100) / 100,
708
- });
1281
+ throw new Error(`Failed to search packages: ${response.statusText}`);
1282
+ }
1283
+ const rawData = await response.json();
1284
+ const parseResult = NpmSearchResultSchema.safeParse(rawData);
1285
+ if (!parseResult.success) {
1286
+ throw new Error('Invalid search results data received');
1287
+ }
1288
+ const { objects, total } = parseResult.data;
1289
+ let text = `🔍 Search results for "${args.query}"\n`;
1290
+ text += `Found ${total.toLocaleString()} packages (showing top ${limit})\n\n`;
1291
+ for (const result of objects) {
1292
+ const pkg = result.package;
1293
+ const score = result.score;
1294
+ text += `📦 ${pkg.name}@${pkg.version}\n`;
1295
+ if (pkg.description)
1296
+ text += `${pkg.description}\n`;
1297
+ // Normalize and format score to ensure it's between 0 and 1
1298
+ const normalizedScore = Math.min(1, score.final / 100);
1299
+ const finalScore = normalizedScore.toFixed(2);
1300
+ text += `Score: ${finalScore} (${(normalizedScore * 100).toFixed(0)}%)\n`;
1301
+ if (pkg.keywords && pkg.keywords.length > 0) {
1302
+ text += `Keywords: ${pkg.keywords.join(', ')}\n`;
1303
+ }
1304
+ if (pkg.links) {
1305
+ text += 'Links:\n';
1306
+ if (pkg.links.npm)
1307
+ text += `• NPM: ${pkg.links.npm}\n`;
1308
+ if (pkg.links.homepage)
1309
+ text += `• Homepage: ${pkg.links.homepage}\n`;
1310
+ if (pkg.links.repository)
1311
+ text += `• Repository: ${pkg.links.repository}\n`;
1312
+ }
1313
+ text += '\n';
1314
+ }
1315
+ return {
1316
+ content: [{ type: 'text', text }],
1317
+ isError: false,
1318
+ };
1319
+ }
1320
+ catch (error) {
1321
+ return {
1322
+ content: [
1323
+ {
1324
+ type: 'text',
1325
+ text: `Error searching packages: ${error instanceof Error ? error.message : 'Unknown error'}`,
1326
+ },
1327
+ ],
1328
+ isError: true,
1329
+ };
1330
+ }
1331
+ }
1332
+ // License compatibility checker
1333
+ async function handleNpmLicenseCompatibility(args) {
1334
+ try {
1335
+ const licenses = await Promise.all(args.packages.map(async (pkg) => {
1336
+ const response = await fetch(`https://registry.npmjs.org/${pkg}/latest`);
1337
+ if (!response.ok) {
1338
+ throw new Error(`Failed to fetch license info for ${pkg}: ${response.statusText}`);
1339
+ }
1340
+ const data = (await response.json());
1341
+ return {
1342
+ package: pkg,
1343
+ license: data.license || 'UNKNOWN',
1344
+ };
1345
+ }));
1346
+ let text = '📜 License Compatibility Analysis\n\n';
1347
+ text += 'Packages analyzed:\n';
1348
+ for (const { package: pkg, license } of licenses) {
1349
+ text += `• ${pkg}: ${license}\n`;
1350
+ }
1351
+ text += '\n';
1352
+ // Basic license compatibility check
1353
+ const hasGPL = licenses.some(({ license }) => license?.includes('GPL'));
1354
+ const hasMIT = licenses.some(({ license }) => license === 'MIT');
1355
+ const hasApache = licenses.some(({ license }) => license?.includes('Apache'));
1356
+ const hasUnknown = licenses.some(({ license }) => license === 'UNKNOWN');
1357
+ text += 'Compatibility Analysis:\n';
1358
+ if (hasUnknown) {
1359
+ text += '⚠️ Warning: Some packages have unknown licenses. Manual review recommended.\n';
1360
+ }
1361
+ if (hasGPL) {
1362
+ text += '⚠️ Contains GPL licensed code. Resulting work may need to be GPL licensed.\n';
1363
+ if (hasMIT || hasApache) {
1364
+ text += '⚠️ Mixed GPL with MIT/Apache licenses. Review carefully for compliance.\n';
1365
+ }
1366
+ }
1367
+ else if (hasMIT && hasApache) {
1368
+ text += '✅ MIT and Apache 2.0 licenses are compatible.\n';
1369
+ }
1370
+ else if (hasMIT) {
1371
+ text += '✅ All MIT licensed. Generally safe to use.\n';
1372
+ }
1373
+ else if (hasApache) {
1374
+ text += '✅ All Apache licensed. Generally safe to use.\n';
1375
+ }
1376
+ text +=
1377
+ '\nNote: This is a basic analysis. For legal compliance, please consult with a legal expert.\n';
1378
+ return {
1379
+ content: [{ type: 'text', text }],
1380
+ isError: false,
1381
+ };
1382
+ }
1383
+ catch (error) {
709
1384
  return {
710
1385
  content: [
711
1386
  {
712
1387
  type: 'text',
713
- text: `Popularity metrics for ${args.packageName}:
714
- - Overall Score: ${result.score}
715
- - GitHub Stars: ${result.stars}
716
- - Downloads: ${result.downloads}
717
- - Dependent Packages: ${result.dependents}
718
- - Community Interest: ${result.communityInterest}`,
1388
+ text: `Error analyzing license compatibility: ${error instanceof Error ? error.message : 'Unknown error'}`,
719
1389
  },
720
1390
  ],
1391
+ isError: true,
1392
+ };
1393
+ }
1394
+ }
1395
+ // Repository statistics analyzer
1396
+ async function handleNpmRepoStats(args) {
1397
+ try {
1398
+ const results = await Promise.all(args.packages.map(async (pkg) => {
1399
+ // First get the package info from npm to find the repository URL
1400
+ const npmResponse = await fetch(`https://registry.npmjs.org/${pkg}/latest`);
1401
+ if (!npmResponse.ok) {
1402
+ throw new Error(`Failed to fetch npm info for ${pkg}: ${npmResponse.statusText}`);
1403
+ }
1404
+ const npmData = (await npmResponse.json());
1405
+ if (!npmData.repository?.url) {
1406
+ return { name: pkg, text: `No repository URL found for package ${pkg}` };
1407
+ }
1408
+ // Extract GitHub repo info from URL
1409
+ const repoUrl = npmData.repository.url;
1410
+ const match = repoUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
1411
+ if (!match) {
1412
+ return { name: pkg, text: `Could not parse GitHub repository URL: ${repoUrl}` };
1413
+ }
1414
+ const [, owner, repo] = match;
1415
+ // Fetch repository stats from GitHub API
1416
+ const githubResponse = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
1417
+ headers: {
1418
+ Accept: 'application/vnd.github.v3+json',
1419
+ 'User-Agent': 'MCP-Server',
1420
+ },
1421
+ });
1422
+ if (!githubResponse.ok) {
1423
+ throw new Error(`Failed to fetch GitHub stats: ${githubResponse.statusText}`);
1424
+ }
1425
+ const data = (await githubResponse.json());
1426
+ const text = [
1427
+ `${'='.repeat(80)}`,
1428
+ `📊 Repository Statistics for ${pkg}`,
1429
+ `${'='.repeat(80)}\n`,
1430
+ '🌟 Engagement Metrics',
1431
+ `${'─'.repeat(40)}`,
1432
+ `• Stars: ${data.stargazers_count.toLocaleString().padEnd(10)} ⭐`,
1433
+ `• Forks: ${data.forks_count.toLocaleString().padEnd(10)} 🔄`,
1434
+ `• Watchers: ${data.watchers_count.toLocaleString().padEnd(10)} 👀`,
1435
+ `• Open Issues: ${data.open_issues_count.toLocaleString().padEnd(10)} 🔍\n`,
1436
+ '📅 Timeline',
1437
+ `${'─'.repeat(40)}`,
1438
+ `• Created: ${new Date(data.created_at).toLocaleDateString()}`,
1439
+ `• Last Updated: ${new Date(data.updated_at).toLocaleDateString()}\n`,
1440
+ '🔧 Repository Details',
1441
+ `${'─'.repeat(40)}`,
1442
+ `• Default Branch: ${data.default_branch}`,
1443
+ `• Wiki Enabled: ${data.has_wiki ? 'Yes' : 'No'}\n`,
1444
+ '🏷️ Topics',
1445
+ `${'─'.repeat(40)}`,
1446
+ data.topics.length
1447
+ ? data.topics.map((topic) => `• ${topic}`).join('\n')
1448
+ : '• No topics found',
1449
+ '',
1450
+ ].join('\n');
1451
+ return { name: pkg, text };
1452
+ }));
1453
+ let text = '';
1454
+ for (const result of results) {
1455
+ text += result.text;
1456
+ if (results.indexOf(result) < results.length - 1) {
1457
+ text += '\n\n';
1458
+ }
1459
+ }
1460
+ return {
1461
+ content: [{ type: 'text', text }],
721
1462
  isError: false,
722
1463
  };
723
1464
  }
@@ -726,7 +1467,276 @@ async function handleNpmPopularity(args) {
726
1467
  content: [
727
1468
  {
728
1469
  type: 'text',
729
- text: `Error fetching popularity metrics: ${error instanceof Error ? error.message : 'Unknown error'}`,
1470
+ text: `Error analyzing repository stats: ${error instanceof Error ? error.message : 'Unknown error'}`,
1471
+ },
1472
+ ],
1473
+ isError: true,
1474
+ };
1475
+ }
1476
+ }
1477
+ async function handleNpmDeprecated(args) {
1478
+ try {
1479
+ const results = await Promise.all(args.packages.map(async (pkg) => {
1480
+ const response = await fetch(`https://registry.npmjs.org/${pkg}`);
1481
+ if (!response.ok) {
1482
+ throw new Error(`Failed to fetch package info: ${response.statusText}`);
1483
+ }
1484
+ const rawData = (await response.json());
1485
+ if (!isNpmPackageInfo(rawData)) {
1486
+ throw new Error('Invalid package info data received');
1487
+ }
1488
+ // Get latest version info
1489
+ const latestVersion = rawData['dist-tags']?.latest;
1490
+ if (!latestVersion || !rawData.versions?.[latestVersion]) {
1491
+ throw new Error('No latest version found');
1492
+ }
1493
+ const latestVersionInfo = rawData.versions[latestVersion];
1494
+ const dependencies = {
1495
+ ...(latestVersionInfo.dependencies || {}),
1496
+ ...(latestVersionInfo.devDependencies || {}),
1497
+ ...(latestVersionInfo.peerDependencies || {}),
1498
+ };
1499
+ // Check each dependency
1500
+ const deprecatedDeps = [];
1501
+ await Promise.all(Object.entries(dependencies).map(async ([dep, version]) => {
1502
+ try {
1503
+ const depResponse = await fetch(`https://registry.npmjs.org/${dep}`);
1504
+ if (!depResponse.ok)
1505
+ return;
1506
+ const depData = (await depResponse.json());
1507
+ const depVersion = version.replace(/[^0-9.]/g, '');
1508
+ if (depData.versions?.[depVersion]?.deprecated) {
1509
+ deprecatedDeps.push({
1510
+ name: dep,
1511
+ version: depVersion,
1512
+ message: depData.versions[depVersion].deprecated || 'No message provided',
1513
+ });
1514
+ }
1515
+ }
1516
+ catch (error) {
1517
+ console.error(`Error checking ${dep}:`, error);
1518
+ }
1519
+ }));
1520
+ // Check if the package itself is deprecated
1521
+ const isDeprecated = latestVersionInfo.deprecated;
1522
+ let text = `📦 Deprecation Check for ${pkg}@${latestVersion}\n\n`;
1523
+ if (isDeprecated) {
1524
+ text += '⚠️ WARNING: This package is deprecated!\n';
1525
+ text += `Deprecation message: ${latestVersionInfo.deprecated}\n\n`;
1526
+ }
1527
+ else {
1528
+ text += '✅ This package is not deprecated\n\n';
1529
+ }
1530
+ if (deprecatedDeps.length > 0) {
1531
+ text += `Found ${deprecatedDeps.length} deprecated dependencies:\n\n`;
1532
+ for (const dep of deprecatedDeps) {
1533
+ text += `⚠️ ${dep.name}@${dep.version}\n`;
1534
+ text += ` Message: ${dep.message}\n\n`;
1535
+ }
1536
+ }
1537
+ else {
1538
+ text += '✅ No deprecated dependencies found\n';
1539
+ }
1540
+ return { name: pkg, text };
1541
+ }));
1542
+ let text = '';
1543
+ for (const result of results) {
1544
+ text += result.text;
1545
+ }
1546
+ return {
1547
+ content: [{ type: 'text', text }],
1548
+ isError: false,
1549
+ };
1550
+ }
1551
+ catch (error) {
1552
+ return {
1553
+ content: [
1554
+ {
1555
+ type: 'text',
1556
+ text: `Error checking deprecated packages: ${error instanceof Error ? error.message : 'Unknown error'}`,
1557
+ },
1558
+ ],
1559
+ isError: true,
1560
+ };
1561
+ }
1562
+ }
1563
+ async function handleNpmChangelogAnalysis(args) {
1564
+ try {
1565
+ const results = await Promise.all(args.packages.map(async (pkg) => {
1566
+ // First get the package info from npm to find the repository URL
1567
+ const npmResponse = await fetch(`https://registry.npmjs.org/${pkg}`);
1568
+ if (!npmResponse.ok) {
1569
+ throw new Error(`Failed to fetch npm info for ${pkg}: ${npmResponse.statusText}`);
1570
+ }
1571
+ const npmData = await npmResponse.json();
1572
+ if (!isNpmPackageInfo(npmData)) {
1573
+ throw new Error('Invalid package info data received');
1574
+ }
1575
+ const repository = npmData.repository?.url;
1576
+ if (!repository) {
1577
+ return { name: pkg, text: `No repository found for package ${pkg}` };
1578
+ }
1579
+ // Extract GitHub repo info from URL
1580
+ const match = repository.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
1581
+ if (!match) {
1582
+ return { name: pkg, text: `Could not parse GitHub repository URL: ${repository}` };
1583
+ }
1584
+ const [, owner, repo] = match;
1585
+ // Check common changelog file names
1586
+ const changelogFiles = [
1587
+ 'CHANGELOG.md',
1588
+ 'changelog.md',
1589
+ 'CHANGES.md',
1590
+ 'changes.md',
1591
+ 'HISTORY.md',
1592
+ 'history.md',
1593
+ 'NEWS.md',
1594
+ 'news.md',
1595
+ 'RELEASES.md',
1596
+ 'releases.md',
1597
+ ];
1598
+ let changelog = null;
1599
+ for (const file of changelogFiles) {
1600
+ try {
1601
+ const response = await fetch(`https://raw.githubusercontent.com/${owner}/${repo}/master/${file}`);
1602
+ if (response.ok) {
1603
+ changelog = await response.text();
1604
+ break;
1605
+ }
1606
+ }
1607
+ catch (error) {
1608
+ console.error(`Error fetching ${file}:`, error);
1609
+ }
1610
+ }
1611
+ // Get release information from GitHub API
1612
+ const githubResponse = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases`, {
1613
+ headers: {
1614
+ Accept: 'application/vnd.github.v3+json',
1615
+ 'User-Agent': 'MCP-Server',
1616
+ },
1617
+ });
1618
+ const releases = (githubResponse.ok ? await githubResponse.json() : []);
1619
+ let text = `📋 Changelog Analysis for ${pkg}\n\n`;
1620
+ // Analyze version history from npm
1621
+ const versions = Object.keys(npmData.versions || {}).sort((a, b) => {
1622
+ const [aMajor = 0, aMinor = 0] = a.split('.').map(Number);
1623
+ const [bMajor = 0, bMinor = 0] = b.split('.').map(Number);
1624
+ return bMajor - aMajor || bMinor - aMinor;
1625
+ });
1626
+ text += '📦 Version History:\n';
1627
+ text += `• Total versions: ${versions.length}\n`;
1628
+ text += `• Latest version: ${versions[0]}\n`;
1629
+ text += `• First version: ${versions[versions.length - 1]}\n\n`;
1630
+ if (changelog) {
1631
+ text += '📝 Changelog found!\n\n';
1632
+ // Extract and analyze the last few versions from changelog
1633
+ const recentChanges = changelog.split('\n').slice(0, 20).join('\n');
1634
+ text += `Recent changes:\n${recentChanges}\n...\n\n`;
1635
+ }
1636
+ else {
1637
+ text += '⚠️ No changelog file found in repository root\n\n';
1638
+ }
1639
+ if (releases.length > 0) {
1640
+ text += '🚀 Recent GitHub Releases:\n\n';
1641
+ for (const release of releases.slice(0, 5)) {
1642
+ text += `${release.tag_name || 'No tag'}\n`;
1643
+ if (release.name)
1644
+ text += `Title: ${release.name}\n`;
1645
+ if (release.published_at)
1646
+ text += `Published: ${new Date(release.published_at).toLocaleDateString()}\n`;
1647
+ text += '\n';
1648
+ }
1649
+ }
1650
+ else {
1651
+ text += 'ℹ️ No GitHub releases found\n';
1652
+ }
1653
+ return { name: pkg, text };
1654
+ }));
1655
+ let text = '';
1656
+ for (const result of results) {
1657
+ text += result.text;
1658
+ }
1659
+ return {
1660
+ content: [{ type: 'text', text }],
1661
+ isError: false,
1662
+ };
1663
+ }
1664
+ catch (error) {
1665
+ return {
1666
+ content: [
1667
+ {
1668
+ type: 'text',
1669
+ text: `Error analyzing changelog: ${error instanceof Error ? error.message : 'Unknown error'}`,
1670
+ },
1671
+ ],
1672
+ isError: true,
1673
+ };
1674
+ }
1675
+ }
1676
+ async function handleNpmAlternatives(args) {
1677
+ try {
1678
+ const results = await Promise.all(args.packages.map(async (pkg) => {
1679
+ const response = await fetch(`https://registry.npmjs.org/-/v1/search?text=keywords:${pkg}&size=10`);
1680
+ if (!response.ok) {
1681
+ throw new Error(`Failed to search for alternatives: ${response.statusText}`);
1682
+ }
1683
+ const data = (await response.json());
1684
+ const alternatives = data.objects;
1685
+ const downloadCounts = await Promise.all(alternatives.map(async (alt) => {
1686
+ try {
1687
+ const response = await fetch(`https://api.npmjs.org/downloads/point/last-month/${alt.package.name}`);
1688
+ if (!response.ok)
1689
+ return 0;
1690
+ const downloadData = (await response.json());
1691
+ return downloadData.downloads;
1692
+ }
1693
+ catch (error) {
1694
+ console.error(`Error fetching download count for ${alt.package.name}:`, error);
1695
+ return 0;
1696
+ }
1697
+ }));
1698
+ // Get original package downloads for comparison
1699
+ const originalDownloads = await fetch(`https://api.npmjs.org/downloads/point/last-month/${pkg}`)
1700
+ .then((res) => res.json())
1701
+ .then((data) => data.downloads)
1702
+ .catch(() => 0);
1703
+ let text = `🔄 Alternative Packages to ${pkg}\n\n`;
1704
+ text += 'Original package:\n';
1705
+ text += `📦 ${pkg}\n`;
1706
+ text += `Downloads: ${originalDownloads.toLocaleString()}/month\n`;
1707
+ text += `Keywords: ${alternatives[0].package.keywords?.join(', ')}\n\n`;
1708
+ text += 'Alternative packages found:\n\n';
1709
+ alternatives.forEach((alt, index) => {
1710
+ const downloads = downloadCounts[index];
1711
+ const score = alt.score.final;
1712
+ text += `${index + 1}. 📦 ${alt.package.name}\n`;
1713
+ if (alt.package.description)
1714
+ text += ` ${alt.package.description}\n`;
1715
+ text += ` Downloads: ${downloads.toLocaleString()}/month\n`;
1716
+ text += ` Score: ${(score * 100).toFixed(0)}%\n`;
1717
+ if (alt.package.links?.repository)
1718
+ text += ` Repo: ${alt.package.links.repository}\n`;
1719
+ if (alt.package.keywords?.length)
1720
+ text += ` Keywords: ${alt.package.keywords.join(', ')}\n`;
1721
+ text += '\n';
1722
+ });
1723
+ return { name: pkg, text };
1724
+ }));
1725
+ let text = '';
1726
+ for (const result of results) {
1727
+ text += result.text;
1728
+ }
1729
+ return {
1730
+ content: [{ type: 'text', text }],
1731
+ isError: false,
1732
+ };
1733
+ }
1734
+ catch (error) {
1735
+ return {
1736
+ content: [
1737
+ {
1738
+ type: 'text',
1739
+ text: `Error finding alternatives: ${error instanceof Error ? error.message : 'Unknown error'}`,
730
1740
  },
731
1741
  ],
732
1742
  isError: true,
@@ -739,32 +1749,114 @@ const server = new McpServer({
739
1749
  version: '1.0.0',
740
1750
  });
741
1751
  // Add NPM tools
742
- server.tool('npmVersions', { packageName: z.string().describe('The name of the package') }, async ({ packageName }) => {
743
- return await handleNpmVersions({ packageName });
1752
+ server.tool('npmVersions', {
1753
+ packages: z.array(z.string()).describe('List of package names to get versions for'),
1754
+ }, async (args) => {
1755
+ return await handleNpmVersions(args);
744
1756
  });
745
- server.tool('npmLatest', { packageName: z.string().describe('The name of the package') }, async ({ packageName }) => {
746
- return await handleNpmLatest({ packageName });
1757
+ server.tool('npmLatest', {
1758
+ packages: z.array(z.string()).describe('List of package names to get latest versions for'),
1759
+ }, async (args) => {
1760
+ return await handleNpmLatest(args);
747
1761
  });
748
- server.tool('npmDeps', { packageName: z.string().describe('The name of the package') }, async ({ packageName }) => {
749
- return await handleNpmDeps({ packageName });
1762
+ server.tool('npmDeps', {
1763
+ packages: z.array(z.string()).describe('List of package names to analyze dependencies for'),
1764
+ }, async (args) => {
1765
+ return await handleNpmDeps(args);
750
1766
  });
751
- server.tool('npmTypes', { packageName: z.string().describe('The name of the package') }, async ({ packageName }) => {
752
- return await handleNpmTypes({ packageName });
1767
+ server.tool('npmTypes', {
1768
+ packages: z.array(z.string()).describe('List of package names to check types for'),
1769
+ }, async (args) => {
1770
+ return await handleNpmTypes(args);
753
1771
  });
754
- server.tool('npmSize', { packageName: z.string().describe('The name of the package') }, async ({ packageName }) => {
755
- return await handleNpmSize({ packageName });
1772
+ server.tool('npmSize', {
1773
+ packages: z.array(z.string()).describe('List of package names to get size information for'),
1774
+ }, async (args) => {
1775
+ return await handleNpmSize(args);
756
1776
  });
757
- server.tool('npmVulnerabilities', { packageName: z.string().describe('The name of the package') }, async ({ packageName }) => {
758
- return await handleNpmVulnerabilities({ packageName });
1777
+ server.tool('npmVulnerabilities', {
1778
+ packages: z.array(z.string()).describe('List of package names to check for vulnerabilities'),
1779
+ }, async (args) => {
1780
+ return await handleNpmVulnerabilities(args);
759
1781
  });
760
1782
  server.tool('npmTrends', {
761
- packageName: z.string().describe('The name of the package'),
762
- period: z.enum(['last-week', 'last-month', 'last-year']).describe('Time period for trends'),
763
- }, async ({ packageName, period }) => {
764
- return await handleNpmTrends({ packageName, period });
1783
+ packages: z.array(z.string()).describe('List of package names to get trends for'),
1784
+ period: z
1785
+ .enum(['last-week', 'last-month', 'last-year'])
1786
+ .describe('Time period for trends. Options: "last-week", "last-month", "last-year"')
1787
+ .optional()
1788
+ .default('last-month'),
1789
+ }, async (args) => {
1790
+ return await handleNpmTrends(args);
1791
+ });
1792
+ server.tool('npmCompare', {
1793
+ packages: z.array(z.string()).describe('List of package names to compare'),
1794
+ }, async (args) => {
1795
+ return await handleNpmCompare(args);
1796
+ });
1797
+ server.tool('npmMaintainers', {
1798
+ packages: z.array(z.string()).describe('List of package names to get maintainers for'),
1799
+ }, async (args) => {
1800
+ return await handleNpmMaintainers(args);
1801
+ });
1802
+ server.tool('npmScore', {
1803
+ packages: z.array(z.string()).describe('List of package names to get scores for'),
1804
+ }, async (args) => {
1805
+ return await handleNpmScore(args);
1806
+ });
1807
+ server.tool('npmPackageReadme', {
1808
+ packages: z.array(z.string()).describe('List of package names to get READMEs for'),
1809
+ }, async (args) => {
1810
+ return await handleNpmPackageReadme(args);
1811
+ });
1812
+ server.tool('npmSearch', {
1813
+ query: z.string().describe('Search query for packages'),
1814
+ limit: z
1815
+ .number()
1816
+ .min(1)
1817
+ .max(50)
1818
+ .optional()
1819
+ .describe('Maximum number of results to return (default: 10)'),
1820
+ }, async (args) => {
1821
+ return await handleNpmSearch(args);
1822
+ });
1823
+ server.tool('npmLicenseCompatibility', {
1824
+ packages: z
1825
+ .array(z.string())
1826
+ .min(1)
1827
+ .describe('List of package names to check for license compatibility'),
1828
+ }, async (args) => {
1829
+ return await handleNpmLicenseCompatibility(args);
1830
+ });
1831
+ server.tool('npmRepoStats', {
1832
+ packages: z.array(z.string()).describe('List of package names to get repository stats for'),
1833
+ }, async (args) => {
1834
+ return await handleNpmRepoStats(args);
1835
+ });
1836
+ server.tool('npmDeprecated', {
1837
+ packages: z.array(z.string()).describe('List of package names to check for deprecation'),
1838
+ }, async (args) => {
1839
+ return await handleNpmDeprecated(args);
1840
+ });
1841
+ server.tool('npmChangelogAnalysis', {
1842
+ packages: z.array(z.string()).describe('List of package names to analyze changelogs for'),
1843
+ }, async (args) => {
1844
+ return await handleNpmChangelogAnalysis(args);
1845
+ });
1846
+ server.tool('npmAlternatives', {
1847
+ packages: z.array(z.string()).describe('List of package names to find alternatives for'),
1848
+ }, async (args) => {
1849
+ return await handleNpmAlternatives(args);
1850
+ });
1851
+ server.tool('npmQuality', {
1852
+ packages: z.array(z.string()).describe('List of package names to analyze'),
1853
+ }, async (args) => {
1854
+ return await handleNpmQuality(args);
765
1855
  });
766
- server.tool('npmCompare', { packages: z.array(z.string()).describe('List of package names to compare') }, async ({ packages }) => {
767
- return await handleNpmCompare({ packages });
1856
+ server.tool('npmMaintenance', {
1857
+ packages: z.array(z.string()).describe('List of package names to analyze'),
1858
+ }, async (args) => {
1859
+ return await handleNpmMaintenance(args);
768
1860
  });
769
1861
  // Start receiving messages on stdin and sending messages on stdout
770
1862
  const transport = new StdioServerTransport();