@nekzus/mcp-server 1.11.8 → 1.12.33

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
@@ -48,7 +48,7 @@ export const NpmMaintainerSchema = z
48
48
  email: z.string().optional(),
49
49
  url: z.string().optional(),
50
50
  })
51
- .passthrough();
51
+ .loose();
52
52
  export const NpmPackageVersionSchema = z
53
53
  .object({
54
54
  name: z.string(),
@@ -63,7 +63,7 @@ export const NpmPackageVersionSchema = z
63
63
  email: z.string().optional(),
64
64
  url: z.string().optional(),
65
65
  })
66
- .passthrough(),
66
+ .loose(),
67
67
  ])
68
68
  .optional(),
69
69
  license: z.string().optional(),
@@ -72,60 +72,60 @@ export const NpmPackageVersionSchema = z
72
72
  type: z.string().optional(),
73
73
  url: z.string().optional(),
74
74
  })
75
- .passthrough()
75
+ .loose()
76
76
  .optional(),
77
77
  bugs: z
78
78
  .object({
79
79
  url: z.string().optional(),
80
80
  })
81
- .passthrough()
81
+ .loose()
82
82
  .optional(),
83
83
  homepage: z.string().optional(),
84
- dependencies: z.record(z.string()).optional(),
85
- devDependencies: z.record(z.string()).optional(),
86
- peerDependencies: z.record(z.string()).optional(),
84
+ dependencies: z.record(z.string(), z.string()).optional(),
85
+ devDependencies: z.record(z.string(), z.string()).optional(),
86
+ peerDependencies: z.record(z.string(), z.string()).optional(),
87
87
  types: z.string().optional(),
88
88
  typings: z.string().optional(),
89
89
  dist: z
90
90
  .object({ shasum: z.string().optional(), tarball: z.string().optional() })
91
- .passthrough()
91
+ .loose()
92
92
  .optional(),
93
93
  })
94
- .passthrough();
94
+ .loose();
95
95
  export const NpmPackageInfoSchema = z
96
96
  .object({
97
97
  name: z.string(),
98
- 'dist-tags': z.record(z.string()),
99
- versions: z.record(NpmPackageVersionSchema),
100
- time: z.record(z.string()).optional(),
98
+ 'dist-tags': z.record(z.string(), z.string()),
99
+ versions: z.record(z.string(), NpmPackageVersionSchema),
100
+ time: z.record(z.string(), z.string()).optional(),
101
101
  repository: z
102
102
  .object({
103
103
  type: z.string().optional(),
104
104
  url: z.string().optional(),
105
105
  })
106
- .passthrough()
106
+ .loose()
107
107
  .optional(),
108
108
  bugs: z
109
109
  .object({
110
110
  url: z.string().optional(),
111
111
  })
112
- .passthrough()
112
+ .loose()
113
113
  .optional(),
114
114
  homepage: z.string().optional(),
115
115
  maintainers: z.array(NpmMaintainerSchema).optional(),
116
116
  })
117
- .passthrough();
117
+ .loose();
118
118
  export const NpmPackageDataSchema = z.object({
119
119
  name: z.string(),
120
120
  version: z.string(),
121
121
  description: z.string().optional(),
122
122
  license: z.string().optional(),
123
- dependencies: z.record(z.string()).optional(),
124
- devDependencies: z.record(z.string()).optional(),
125
- peerDependencies: z.record(z.string()).optional(),
123
+ dependencies: z.record(z.string(), z.string()).optional(),
124
+ devDependencies: z.record(z.string(), z.string()).optional(),
125
+ peerDependencies: z.record(z.string(), z.string()).optional(),
126
126
  types: z.string().optional(),
127
127
  typings: z.string().optional(),
128
- });
128
+ }).loose();
129
129
  export const BundlephobiaDataSchema = z.object({
130
130
  size: z.number(),
131
131
  gzip: z.number(),
@@ -231,7 +231,7 @@ export const NpmSearchResultSchema = z
231
231
  })),
232
232
  total: z.number(), // total is a sibling of objects
233
233
  })
234
- .passthrough();
234
+ .loose();
235
235
  // Logger function that uses stderr - only for critical errors
236
236
  const log = (...args) => {
237
237
  // Filter out server status messages
@@ -257,9 +257,15 @@ function isNpmPackageInfo(data) {
257
257
  }
258
258
  function isNpmPackageData(data) {
259
259
  try {
260
- return NpmPackageDataSchema.parse(data) !== null;
260
+ // Use safeParse to get error details
261
+ const result = NpmPackageDataSchema.safeParse(data);
262
+ if (!result.success) {
263
+ console.error('isNpmPackageData validation failed:', JSON.stringify(result.error.issues, null, 2));
264
+ }
265
+ return result.success;
261
266
  }
262
- catch {
267
+ catch (e) {
268
+ console.error('isNpmPackageData threw exception:', e);
263
269
  return false;
264
270
  }
265
271
  }
@@ -273,7 +279,11 @@ function isBundlephobiaData(data) {
273
279
  }
274
280
  function isNpmDownloadsData(data) {
275
281
  try {
276
- return NpmDownloadsDataSchema.parse(data) !== null;
282
+ const result = NpmDownloadsDataSchema.safeParse(data);
283
+ if (!result.success) {
284
+ console.error('isNpmDownloadsData validation failed:', JSON.stringify(result.error.issues, null, 2));
285
+ }
286
+ return result.success;
277
287
  }
278
288
  catch {
279
289
  return false;
@@ -909,15 +919,67 @@ export async function handleNpmSize(args) {
909
919
  };
910
920
  }
911
921
  }
922
+ // Helper to detect dependencies
923
+ async function getPackageDependencies(pkgName, version) {
924
+ try {
925
+ const response = await fetch(`https://registry.npmjs.org/${pkgName}/${version}`, {
926
+ headers: { Accept: 'application/json', 'User-Agent': 'NPM-Sentinel-MCP' },
927
+ });
928
+ if (!response.ok)
929
+ return { dependencies: {}, devDependencies: {} };
930
+ const data = (await response.json());
931
+ return {
932
+ dependencies: data.dependencies || {},
933
+ devDependencies: data.devDependencies || {},
934
+ };
935
+ }
936
+ catch (error) {
937
+ console.error(`Error fetching dependencies for ${pkgName}:`, error);
938
+ return { dependencies: {}, devDependencies: {} };
939
+ }
940
+ }
912
941
  export async function handleNpmVulnerabilities(args) {
913
942
  try {
914
943
  const packagesToProcess = args.packages || [];
915
944
  if (packagesToProcess.length === 0) {
916
945
  throw new Error('No package names provided');
917
946
  }
918
- const processedResults = await Promise.all(packagesToProcess.map(async (pkgInput) => {
947
+ // Prepare batch query, checking cache first
948
+ const finalBatchQueries = [];
949
+ const packageMap = new Map();
950
+ const cachedResultsMap = new Map();
951
+ const addToQuery = (name, releaseVersion, isDep) => {
952
+ const version = releaseVersion === 'latest' ? undefined : releaseVersion;
953
+ const key = `${name}@${version || 'latest'}`;
954
+ if (packageMap.has(key))
955
+ return; // Already requested/processed
956
+ // Check Cache
957
+ const cacheKey = generateCacheKey('handleNpmVulnerabilities', name, version || 'all');
958
+ const cachedData = cacheGet(cacheKey);
959
+ if (cachedData) {
960
+ // Store cached result directly using the same structure as we will build later
961
+ cachedResultsMap.set(key, {
962
+ package: `${name}${version ? `@${version}` : ''}`,
963
+ isDependency: isDep,
964
+ vulnerabilities: cachedData.vulnerabilities,
965
+ count: cachedData.vulnerabilities.length,
966
+ status: cachedData.vulnerabilities.length > 0 ? 'vulnerable' : 'secure',
967
+ source: 'cache'
968
+ });
969
+ packageMap.set(key, { name, version, isDependency: isDep });
970
+ }
971
+ else {
972
+ // Not in cache, add to API query
973
+ packageMap.set(key, { name, version, isDependency: isDep });
974
+ finalBatchQueries.push({
975
+ package: { name, ecosystem: 'npm' },
976
+ version: version === 'latest' ? undefined : version,
977
+ });
978
+ }
979
+ };
980
+ await Promise.all(packagesToProcess.map(async (pkgInput) => {
919
981
  let name = '';
920
- let version = undefined;
982
+ let version = 'latest';
921
983
  if (typeof pkgInput === 'string') {
922
984
  const atIdx = pkgInput.lastIndexOf('@');
923
985
  if (atIdx > 0) {
@@ -928,142 +990,94 @@ export async function handleNpmVulnerabilities(args) {
928
990
  name = pkgInput;
929
991
  }
930
992
  }
931
- else if (typeof pkgInput === 'object' && pkgInput !== null) {
932
- name = pkgInput.name;
933
- version = pkgInput.version;
934
- }
935
- const packageNameForOutput = version ? `${name}@${version}` : name;
936
- const cacheKey = generateCacheKey('handleNpmVulnerabilities', name, version || 'all');
937
- const cachedData = cacheGet(cacheKey);
938
- if (cachedData) {
939
- return {
940
- package: packageNameForOutput,
941
- versionQueried: version || null,
942
- status: 'success_cache',
943
- vulnerabilities: cachedData.vulnerabilities,
944
- message: `${cachedData.message} (from cache)`,
945
- };
946
- }
947
- const osvBody = {
948
- package: {
949
- name,
950
- ecosystem: 'npm',
951
- },
952
- };
953
- if (version) {
954
- osvBody.version = version;
993
+ else {
994
+ return; // Skip invalid
995
+ }
996
+ // Add main package
997
+ addToQuery(name, version, false);
998
+ // Add dependencies (only if version specified)
999
+ if (version !== 'latest') {
1000
+ const { dependencies } = await getPackageDependencies(name, version);
1001
+ for (const [depName, depVersion] of Object.entries(dependencies)) {
1002
+ const cleanVersion = depVersion.replace(/[\^~]/g, '');
1003
+ if (/^\d+\.\d+\.\d+/.test(cleanVersion)) {
1004
+ addToQuery(depName, cleanVersion, true);
1005
+ }
1006
+ }
955
1007
  }
956
- const response = await fetch('https://api.osv.dev/v1/query', {
1008
+ }));
1009
+ let apiResults = [];
1010
+ if (finalBatchQueries.length > 0) {
1011
+ // Perform Batch API call to OSV for non-cached items
1012
+ const response = await fetch('https://api.osv.dev/v1/querybatch', {
957
1013
  method: 'POST',
958
- headers: {
959
- 'Content-Type': 'application/json',
960
- },
961
- body: JSON.stringify(osvBody),
1014
+ headers: { 'Content-Type': 'application/json' },
1015
+ body: JSON.stringify({ queries: finalBatchQueries }),
962
1016
  });
963
- const queryVersionSpecified = !!version;
964
1017
  if (!response.ok) {
965
- const errorResult = {
966
- package: packageNameForOutput,
967
- versionQueried: version || null,
968
- status: 'error',
969
- error: `OSV API Error: ${response.statusText}`,
970
- vulnerabilities: [],
971
- };
972
- // Do not cache error responses from OSV API as they might be temporary
973
- return errorResult;
974
- }
975
- const data = (await response.json());
976
- const vulns = data.vulns || [];
977
- let message;
978
- if (vulns.length === 0) {
979
- message = `No known vulnerabilities found${queryVersionSpecified ? ' for the specified version' : ''}.`;
980
- }
981
- else {
982
- message = `${vulns.length} vulnerability(ies) found${queryVersionSpecified ? ' for the specified version' : ''}.`;
1018
+ throw new Error(`OSV Batch API Error: ${response.status} ${response.statusText}`);
983
1019
  }
1020
+ const batchData = (await response.json());
1021
+ apiResults = batchData.results || [];
1022
+ }
1023
+ // Reconstruct all results (Cache + API)
1024
+ // We iterate over the packageMap to maintain order essentially, or we reconstruct based on what we see
1025
+ // Since map iteration order is insertion order, we can use that to return results generally in order of discovery
1026
+ // Map API results back to their query keys to merge easily
1027
+ const apiResultsMap = new Map();
1028
+ finalBatchQueries.forEach((query, index) => {
1029
+ const vulns = apiResults[index]?.vulns || [];
1030
+ const pkgName = query.package.name;
1031
+ const pkgVersion = query.version;
1032
+ const key = `${pkgName}@${pkgVersion || 'latest'}`;
1033
+ apiResultsMap.set(key, vulns);
1034
+ });
1035
+ const processedResults = [];
1036
+ for (const [key, info] of packageMap.entries()) {
1037
+ if (cachedResultsMap.has(key)) {
1038
+ processedResults.push(cachedResultsMap.get(key));
1039
+ continue;
1040
+ }
1041
+ // Process API result
1042
+ const vulns = apiResultsMap.get(key) || [];
984
1043
  const processedVulns = vulns.map((vuln) => {
985
- const sev = typeof vuln.severity === 'object'
986
- ? vuln.severity.type || 'Unknown'
987
- : vuln.severity || 'Unknown';
1044
+ const sev = typeof vuln.severity === 'object' ? vuln.severity.type || 'Unknown' : vuln.severity || 'Unknown';
988
1045
  const refs = vuln.references ? vuln.references.map((r) => r.url) : [];
989
- const affectedRanges = [];
990
- const affectedVersionsListed = [];
991
1046
  const vulnerabilityDetails = {
992
- summary: vuln.summary,
1047
+ id: vuln.id,
1048
+ summary: vuln.summary || 'No summary available',
993
1049
  severity: sev,
994
1050
  references: refs,
1051
+ aliases: vuln.aliases || [],
1052
+ modified: vuln.modified,
1053
+ published: vuln.published,
995
1054
  };
996
- if (vuln.affected && vuln.affected.length > 0) {
997
- const lifecycle = {};
998
- const firstAffectedEvents = vuln.affected[0]?.ranges?.[0]?.events;
999
- if (firstAffectedEvents) {
1000
- const introducedEvent = firstAffectedEvents.find((e) => e.introduced);
1001
- const fixedEvent = firstAffectedEvents.find((e) => e.fixed);
1002
- if (introducedEvent?.introduced)
1003
- lifecycle.introduced = introducedEvent.introduced;
1004
- if (fixedEvent?.fixed)
1005
- lifecycle.fixed = fixedEvent.fixed;
1006
- }
1007
- if (Object.keys(lifecycle).length > 0) {
1008
- vulnerabilityDetails.lifecycle = lifecycle;
1009
- if (queryVersionSpecified && version && lifecycle.fixed) {
1010
- const queriedParts = version.split('.').map(Number);
1011
- const fixedParts = lifecycle.fixed.split('.').map(Number);
1012
- let isFixedDecision = false;
1013
- const maxLength = Math.max(queriedParts.length, fixedParts.length);
1014
- for (let i = 0; i < maxLength; i++) {
1015
- const qp = queriedParts[i] || 0;
1016
- const fp = fixedParts[i] || 0;
1017
- if (fp < qp) {
1018
- isFixedDecision = true;
1019
- break;
1020
- }
1021
- if (fp > qp) {
1022
- isFixedDecision = false;
1023
- break;
1024
- }
1025
- if (i === maxLength - 1) {
1026
- isFixedDecision = fixedParts.length <= queriedParts.length;
1027
- }
1028
- }
1029
- vulnerabilityDetails.isFixedInQueriedVersion = isFixedDecision;
1030
- }
1031
- }
1032
- }
1033
- if (!queryVersionSpecified && vuln.affected) {
1034
- for (const aff of vuln.affected) {
1035
- if (aff.ranges) {
1036
- for (const range of aff.ranges) {
1037
- affectedRanges.push({ type: range.type, events: range.events });
1038
- }
1039
- }
1040
- if (aff.versions && aff.versions.length > 0) {
1041
- affectedVersionsListed.push(...aff.versions);
1042
- }
1043
- }
1044
- if (affectedRanges.length > 0) {
1045
- vulnerabilityDetails.affectedRanges = affectedRanges;
1046
- }
1047
- if (affectedVersionsListed.length > 0) {
1048
- vulnerabilityDetails.affectedVersionsListed = affectedVersionsListed;
1049
- }
1050
- }
1055
+ if (vuln.affected)
1056
+ vulnerabilityDetails.affected = vuln.affected;
1051
1057
  return vulnerabilityDetails;
1052
1058
  });
1053
- const resultToCache = {
1059
+ const resultEntry = {
1060
+ package: `${info.name}${info.version && info.version !== 'latest' && info.version !== undefined ? `@${info.version}` : ''}`,
1061
+ isDependency: info.isDependency,
1054
1062
  vulnerabilities: processedVulns,
1055
- message: message,
1063
+ count: processedVulns.length,
1064
+ status: processedVulns.length > 0 ? 'vulnerable' : 'secure',
1065
+ message: processedVulns.length > 0 ? `${processedVulns.length} vulnerability(ies) found` : 'No known vulnerabilities found',
1056
1066
  };
1057
- cacheSet(cacheKey, resultToCache, CACHE_TTL_MEDIUM);
1058
- return {
1059
- package: packageNameForOutput,
1060
- versionQueried: version || null,
1061
- status: 'success',
1067
+ processedResults.push(resultEntry);
1068
+ // Cache this result for future
1069
+ const cacheKey = generateCacheKey('handleNpmVulnerabilities', info.name, info.version || 'all');
1070
+ cacheSet(cacheKey, {
1062
1071
  vulnerabilities: processedVulns,
1063
- message: message,
1064
- };
1065
- }));
1066
- const responseJson = JSON.stringify({ results: processedResults }, null, 2);
1072
+ message: `${processedVulns.length} vulnerabilities found`
1073
+ }, CACHE_TTL_MEDIUM);
1074
+ }
1075
+ // Filter final output
1076
+ const finalOutput = processedResults.filter(r => r.count > 0 || !r.isDependency);
1077
+ const responseJson = JSON.stringify({
1078
+ summary: `Scanned ${packageMap.size} packages (including dependencies). Found vulnerabilities in ${finalOutput.filter(r => r.count > 0).length} packages. (${cachedResultsMap.size} from cache, ${finalBatchQueries.length} from API)`,
1079
+ results: finalOutput
1080
+ }, null, 2);
1067
1081
  return { content: [{ type: 'text', text: responseJson }], isError: false };
1068
1082
  }
1069
1083
  catch (error) {
@@ -3091,17 +3105,13 @@ export default function createServer({ config, }) {
3091
3105
  // Create server instance
3092
3106
  const server = new McpServer({
3093
3107
  name: 'npm-sentinel-mcp',
3094
- version: '1.11.8',
3095
- capabilities: {
3096
- resources: {},
3097
- },
3108
+ version: '1.12.0',
3098
3109
  });
3099
3110
  // Update paths to be relative to the package
3100
3111
  const README_PATH = path.join(packageRoot, 'README.md');
3101
3112
  const LLMS_FULL_TEXT_PATH = path.join(packageRoot, 'llms-full.txt');
3102
3113
  // Register README.md resource
3103
- server.resource('serverReadme', 'doc://server/readme', {
3104
- name: 'Server README',
3114
+ server.registerResource('serverReadme', 'doc://server/readme', {
3105
3115
  description: 'Main documentation and usage guide for this NPM Info Server.',
3106
3116
  mimeType: 'text/markdown',
3107
3117
  }, async (uri) => {
@@ -3127,8 +3137,7 @@ export default function createServer({ config, }) {
3127
3137
  }
3128
3138
  });
3129
3139
  // Register llms-full.txt resource (MCP Specification)
3130
- server.resource('mcpSpecification', 'doc://mcp/specification', {
3131
- name: 'MCP Full Specification',
3140
+ server.registerResource('mcpSpecification', 'doc://mcp/specification', {
3132
3141
  description: 'The llms-full.txt content providing a comprehensive overview of the Model Context Protocol.',
3133
3142
  mimeType: 'text/plain',
3134
3143
  }, async (uri) => {
@@ -3154,207 +3163,283 @@ export default function createServer({ config, }) {
3154
3163
  }
3155
3164
  });
3156
3165
  // Add NPM tools - Ensuring each tool registration is complete and correct
3157
- server.tool('npmVersions', 'Get all available versions of an NPM package', {
3158
- packages: z.array(z.string()).describe('List of package names to get versions for'),
3159
- }, {
3160
- title: 'Get All Package Versions',
3161
- readOnlyHint: true,
3162
- openWorldHint: true,
3163
- idempotentHint: true,
3166
+ server.registerTool('npmVersions', {
3167
+ description: 'Get all available versions of an NPM package',
3168
+ inputSchema: {
3169
+ packages: z.array(z.string()).describe('List of package names to get versions for'),
3170
+ },
3171
+ annotations: {
3172
+ title: 'Get All Package Versions',
3173
+ readOnlyHint: true,
3174
+ openWorldHint: true,
3175
+ idempotentHint: true,
3176
+ },
3164
3177
  }, async (args) => {
3165
3178
  return await handleNpmVersions(args);
3166
3179
  });
3167
- server.tool('npmLatest', 'Get the latest version and changelog of an NPM package', {
3168
- packages: z.array(z.string()).describe('List of package names to get latest versions for'),
3169
- }, {
3170
- title: 'Get Latest Package Information',
3171
- readOnlyHint: true,
3172
- openWorldHint: true,
3173
- idempotentHint: true, // Result for 'latest' tag can change, but call itself is idempotent
3180
+ server.registerTool('npmLatest', {
3181
+ description: 'Get the latest version and changelog of an NPM package',
3182
+ inputSchema: {
3183
+ packages: z.array(z.string()).describe('List of package names to get latest versions for'),
3184
+ },
3185
+ annotations: {
3186
+ title: 'Get Latest Package Information',
3187
+ readOnlyHint: true,
3188
+ openWorldHint: true,
3189
+ idempotentHint: true, // Result for 'latest' tag can change, but call itself is idempotent
3190
+ },
3174
3191
  }, async (args) => {
3175
3192
  return await handleNpmLatest(args);
3176
3193
  });
3177
- server.tool('npmDeps', 'Analyze dependencies and devDependencies of an NPM package', {
3178
- packages: z.array(z.string()).describe('List of package names to analyze dependencies for'),
3179
- }, {
3180
- title: 'Get Package Dependencies',
3181
- readOnlyHint: true,
3182
- openWorldHint: true,
3183
- idempotentHint: true,
3194
+ server.registerTool('npmDeps', {
3195
+ description: 'Analyze dependencies and devDependencies of an NPM package',
3196
+ inputSchema: {
3197
+ packages: z.array(z.string()).describe('List of package names to analyze dependencies for'),
3198
+ },
3199
+ annotations: {
3200
+ title: 'Get Package Dependencies',
3201
+ readOnlyHint: true,
3202
+ openWorldHint: true,
3203
+ idempotentHint: true,
3204
+ },
3184
3205
  }, async (args) => {
3185
3206
  return await handleNpmDeps(args);
3186
3207
  });
3187
- server.tool('npmTypes', 'Check TypeScript types availability and version for a package', {
3188
- packages: z.array(z.string()).describe('List of package names to check types for'),
3189
- }, {
3190
- title: 'Check TypeScript Type Availability',
3191
- readOnlyHint: true,
3192
- openWorldHint: true,
3193
- idempotentHint: true,
3208
+ server.registerTool('npmTypes', {
3209
+ description: 'Check TypeScript types availability and version for a package',
3210
+ inputSchema: {
3211
+ packages: z.array(z.string()).describe('List of package names to check types for'),
3212
+ },
3213
+ annotations: {
3214
+ title: 'Check TypeScript Type Availability',
3215
+ readOnlyHint: true,
3216
+ openWorldHint: true,
3217
+ idempotentHint: true,
3218
+ },
3194
3219
  }, async (args) => {
3195
3220
  return await handleNpmTypes(args);
3196
3221
  });
3197
- server.tool('npmSize', 'Get package size information including dependencies and bundle size', {
3198
- packages: z.array(z.string()).describe('List of package names to get size information for'),
3199
- }, {
3200
- title: 'Get Package Size (Bundlephobia)',
3201
- readOnlyHint: true,
3202
- openWorldHint: true,
3203
- idempotentHint: true,
3222
+ server.registerTool('npmSize', {
3223
+ description: 'Get package size information including dependencies and bundle size',
3224
+ inputSchema: {
3225
+ packages: z.array(z.string()).describe('List of package names to get size information for'),
3226
+ },
3227
+ annotations: {
3228
+ title: 'Get Package Size (Bundlephobia)',
3229
+ readOnlyHint: true,
3230
+ openWorldHint: true,
3231
+ idempotentHint: true,
3232
+ },
3204
3233
  }, async (args) => {
3205
3234
  return await handleNpmSize(args);
3206
3235
  });
3207
- server.tool('npmVulnerabilities', 'Check for known vulnerabilities in packages', {
3208
- packages: z.array(z.string()).describe('List of package names to check for vulnerabilities'),
3209
- }, {
3210
- title: 'Check Package Vulnerabilities (OSV.dev)',
3211
- readOnlyHint: true,
3212
- openWorldHint: true,
3213
- idempotentHint: false, // Vulnerability data can change frequently
3236
+ server.registerTool('npmVulnerabilities', {
3237
+ description: 'Check for known vulnerabilities in packages',
3238
+ inputSchema: {
3239
+ packages: z.array(z.string()).describe('List of package names to check for vulnerabilities'),
3240
+ },
3241
+ annotations: {
3242
+ title: 'Check Package Vulnerabilities (OSV.dev)',
3243
+ readOnlyHint: true,
3244
+ openWorldHint: true,
3245
+ idempotentHint: false, // Vulnerability data can change frequently
3246
+ },
3214
3247
  }, async (args) => {
3215
3248
  return await handleNpmVulnerabilities(args);
3216
3249
  });
3217
- server.tool('npmTrends', 'Get download trends and popularity metrics for packages', {
3218
- packages: z.array(z.string()).describe('List of package names to get trends for'),
3219
- period: z
3220
- .enum(['last-week', 'last-month', 'last-year'])
3221
- .describe('Time period for trends. Options: "last-week", "last-month", "last-year"')
3222
- .optional()
3223
- .default('last-month'),
3224
- }, {
3225
- title: 'Get NPM Package Download Trends',
3226
- readOnlyHint: true,
3227
- openWorldHint: true,
3228
- idempotentHint: true, // Trends for a fixed past period are idempotent
3250
+ server.registerTool('npmTrends', {
3251
+ description: 'Get download trends and popularity metrics for packages',
3252
+ inputSchema: {
3253
+ packages: z.array(z.string()).describe('List of package names to get trends for'),
3254
+ period: z
3255
+ .enum(['last-week', 'last-month', 'last-year'])
3256
+ .describe('Time period for trends. Options: "last-week", "last-month", "last-year"')
3257
+ .optional()
3258
+ .default('last-month'),
3259
+ },
3260
+ annotations: {
3261
+ title: 'Get NPM Package Download Trends',
3262
+ readOnlyHint: true,
3263
+ openWorldHint: true,
3264
+ idempotentHint: true, // Trends for a fixed past period are idempotent
3265
+ },
3229
3266
  }, async (args) => {
3230
3267
  return await handleNpmTrends(args);
3231
3268
  });
3232
- server.tool('npmCompare', 'Compare multiple NPM packages based on various metrics', {
3233
- packages: z.array(z.string()).describe('List of package names to compare'),
3234
- }, {
3235
- title: 'Compare NPM Packages',
3236
- readOnlyHint: true,
3237
- openWorldHint: true,
3238
- idempotentHint: true,
3269
+ server.registerTool('npmCompare', {
3270
+ description: 'Compare multiple NPM packages based on various metrics',
3271
+ inputSchema: {
3272
+ packages: z.array(z.string()).describe('List of package names to compare'),
3273
+ },
3274
+ annotations: {
3275
+ title: 'Compare NPM Packages',
3276
+ readOnlyHint: true,
3277
+ openWorldHint: true,
3278
+ idempotentHint: true,
3279
+ },
3239
3280
  }, async (args) => {
3240
3281
  return await handleNpmCompare(args);
3241
3282
  });
3242
- server.tool('npmMaintainers', 'Get maintainers information for NPM packages', {
3243
- packages: z.array(z.string()).describe('List of package names to get maintainers for'),
3244
- }, {
3245
- title: 'Get NPM Package Maintainers',
3246
- readOnlyHint: true,
3247
- openWorldHint: true,
3248
- idempotentHint: true,
3283
+ server.registerTool('npmMaintainers', {
3284
+ description: 'Get maintainers information for NPM packages',
3285
+ inputSchema: {
3286
+ packages: z.array(z.string()).describe('List of package names to get maintainers for'),
3287
+ },
3288
+ annotations: {
3289
+ title: 'Get NPM Package Maintainers',
3290
+ readOnlyHint: true,
3291
+ openWorldHint: true,
3292
+ idempotentHint: true,
3293
+ },
3249
3294
  }, async (args) => {
3250
3295
  return await handleNpmMaintainers(args);
3251
3296
  });
3252
- server.tool('npmScore', 'Get consolidated package score based on quality, maintenance, and popularity metrics', {
3253
- packages: z.array(z.string()).describe('List of package names to get scores for'),
3254
- }, {
3255
- title: 'Get NPM Package Score (NPMS.io)',
3256
- readOnlyHint: true,
3257
- openWorldHint: true,
3258
- idempotentHint: true, // Score for a version is stable, for 'latest' can change
3297
+ server.registerTool('npmScore', {
3298
+ description: 'Get consolidated package score based on quality, maintenance, and popularity metrics',
3299
+ inputSchema: {
3300
+ packages: z.array(z.string()).describe('List of package names to get scores for'),
3301
+ },
3302
+ annotations: {
3303
+ title: 'Get NPM Package Score (NPMS.io)',
3304
+ readOnlyHint: true,
3305
+ openWorldHint: true,
3306
+ idempotentHint: true, // Score for a version is stable, for 'latest' can change
3307
+ },
3259
3308
  }, async (args) => {
3260
3309
  return await handleNpmScore(args);
3261
3310
  });
3262
- server.tool('npmPackageReadme', 'Get the README content for NPM packages', {
3263
- packages: z.array(z.string()).describe('List of package names to get READMEs for'),
3264
- }, {
3265
- title: 'Get NPM Package README',
3266
- readOnlyHint: true,
3267
- openWorldHint: true,
3268
- idempotentHint: true,
3311
+ server.registerTool('npmPackageReadme', {
3312
+ description: 'Get the README content for NPM packages',
3313
+ inputSchema: {
3314
+ packages: z.array(z.string()).describe('List of package names to get READMEs for'),
3315
+ },
3316
+ annotations: {
3317
+ title: 'Get NPM Package README',
3318
+ readOnlyHint: true,
3319
+ openWorldHint: true,
3320
+ idempotentHint: true,
3321
+ },
3269
3322
  }, async (args) => {
3270
3323
  return await handleNpmPackageReadme(args);
3271
3324
  });
3272
- server.tool('npmSearch', 'Search for NPM packages with optional limit', {
3273
- query: z.string().describe('Search query for packages'),
3274
- limit: z
3275
- .number()
3276
- .min(1)
3277
- .max(50)
3278
- .optional()
3279
- .describe('Maximum number of results to return (default: 10)'),
3280
- }, {
3281
- title: 'Search NPM Packages',
3282
- readOnlyHint: true,
3283
- openWorldHint: true,
3284
- idempotentHint: false, // Search results can change
3325
+ server.registerTool('npmSearch', {
3326
+ description: 'Search for NPM packages with optional limit',
3327
+ inputSchema: {
3328
+ query: z.string().describe('Search query for packages'),
3329
+ limit: z
3330
+ .number()
3331
+ .min(1)
3332
+ .max(50)
3333
+ .optional()
3334
+ .describe('Maximum number of results to return (default: 10)'),
3335
+ },
3336
+ annotations: {
3337
+ title: 'Search NPM Packages',
3338
+ readOnlyHint: true,
3339
+ openWorldHint: true,
3340
+ idempotentHint: false, // Search results can change
3341
+ },
3285
3342
  }, async (args) => {
3286
3343
  return await handleNpmSearch(args);
3287
3344
  });
3288
- server.tool('npmLicenseCompatibility', 'Check license compatibility between multiple packages', {
3289
- packages: z
3290
- .array(z.string())
3291
- .min(1)
3292
- .describe('List of package names to check for license compatibility'),
3293
- }, {
3294
- title: 'Check NPM License Compatibility',
3295
- readOnlyHint: true,
3296
- openWorldHint: true,
3297
- idempotentHint: true,
3345
+ server.registerTool('npmLicenseCompatibility', {
3346
+ description: 'Check license compatibility between multiple packages',
3347
+ inputSchema: {
3348
+ packages: z
3349
+ .array(z.string())
3350
+ .min(1)
3351
+ .describe('List of package names to check for license compatibility'),
3352
+ },
3353
+ annotations: {
3354
+ title: 'Check NPM License Compatibility',
3355
+ readOnlyHint: true,
3356
+ openWorldHint: true,
3357
+ idempotentHint: true,
3358
+ },
3298
3359
  }, async (args) => {
3299
3360
  return await handleNpmLicenseCompatibility(args);
3300
3361
  });
3301
- server.tool('npmRepoStats', 'Get repository statistics for NPM packages', {
3302
- packages: z.array(z.string()).describe('List of package names to get repository stats for'),
3303
- }, {
3304
- title: 'Get NPM Package Repository Stats (GitHub)',
3305
- readOnlyHint: true,
3306
- openWorldHint: true,
3307
- idempotentHint: true, // Stats for a repo at a point in time, though they change over time
3362
+ server.registerTool('npmRepoStats', {
3363
+ description: 'Get repository statistics for NPM packages',
3364
+ inputSchema: {
3365
+ packages: z.array(z.string()).describe('List of package names to get repository stats for'),
3366
+ },
3367
+ annotations: {
3368
+ title: 'Get NPM Package Repository Stats (GitHub)',
3369
+ readOnlyHint: true,
3370
+ openWorldHint: true,
3371
+ idempotentHint: true, // Stats for a repo at a point in time, though they change over time
3372
+ },
3308
3373
  }, async (args) => {
3309
3374
  return await handleNpmRepoStats(args);
3310
3375
  });
3311
- server.tool('npmDeprecated', 'Check if packages are deprecated', {
3312
- packages: z.array(z.string()).describe('List of package names to check for deprecation'),
3313
- }, {
3314
- title: 'Check NPM Package Deprecation Status',
3315
- readOnlyHint: true,
3316
- openWorldHint: true,
3317
- idempotentHint: true, // Deprecation status is generally stable for a version
3376
+ server.registerTool('npmDeprecated', {
3377
+ description: 'Check if packages are deprecated',
3378
+ inputSchema: {
3379
+ packages: z.array(z.string()).describe('List of package names to check for deprecation'),
3380
+ },
3381
+ annotations: {
3382
+ title: 'Check NPM Package Deprecation Status',
3383
+ readOnlyHint: true,
3384
+ openWorldHint: true,
3385
+ idempotentHint: true, // Deprecation status is generally stable for a version
3386
+ },
3318
3387
  }, async (args) => {
3319
3388
  return await handleNpmDeprecated(args);
3320
3389
  });
3321
- server.tool('npmChangelogAnalysis', 'Analyze changelog and release history of packages', {
3322
- packages: z.array(z.string()).describe('List of package names to analyze changelogs for'),
3323
- }, {
3324
- title: 'Analyze NPM Package Changelog (GitHub)',
3325
- readOnlyHint: true,
3326
- openWorldHint: true,
3327
- idempotentHint: true,
3390
+ server.registerTool('npmChangelogAnalysis', {
3391
+ description: 'Analyze changelog and release history of packages',
3392
+ inputSchema: {
3393
+ packages: z.array(z.string()).describe('List of package names to analyze changelogs for'),
3394
+ },
3395
+ annotations: {
3396
+ title: 'Analyze NPM Package Changelog (GitHub)',
3397
+ readOnlyHint: true,
3398
+ openWorldHint: true,
3399
+ idempotentHint: true,
3400
+ },
3328
3401
  }, async (args) => {
3329
3402
  return await handleNpmChangelogAnalysis(args);
3330
3403
  });
3331
- server.tool('npmAlternatives', 'Find alternative packages with similar functionality', {
3332
- packages: z.array(z.string()).describe('List of package names to find alternatives for'),
3333
- }, {
3334
- title: 'Find NPM Package Alternatives',
3335
- readOnlyHint: true,
3336
- openWorldHint: true,
3337
- idempotentHint: false, // Search-based, results can change
3404
+ server.registerTool('npmAlternatives', {
3405
+ description: 'Find alternative packages with similar functionality',
3406
+ inputSchema: {
3407
+ packages: z.array(z.string()).describe('List of package names to find alternatives for'),
3408
+ },
3409
+ annotations: {
3410
+ title: 'Find NPM Package Alternatives',
3411
+ readOnlyHint: true,
3412
+ openWorldHint: true,
3413
+ idempotentHint: false, // Search-based, results can change
3414
+ },
3338
3415
  }, async (args) => {
3339
3416
  return await handleNpmAlternatives(args);
3340
3417
  });
3341
- server.tool('npmQuality', 'Analyze package quality metrics', {
3342
- packages: z.array(z.string()).describe('List of package names to analyze'),
3343
- }, {
3344
- title: 'Analyze NPM Package Quality (NPMS.io)',
3345
- readOnlyHint: true,
3346
- openWorldHint: true,
3347
- idempotentHint: true, // Score for a version is stable, for 'latest' can change
3418
+ server.registerTool('npmQuality', {
3419
+ description: 'Analyze package quality metrics',
3420
+ inputSchema: {
3421
+ packages: z.array(z.string()).describe('List of package names to analyze'),
3422
+ },
3423
+ annotations: {
3424
+ title: 'Analyze NPM Package Quality (NPMS.io)',
3425
+ readOnlyHint: true,
3426
+ openWorldHint: true,
3427
+ idempotentHint: true, // Score for a version is stable, for 'latest' can change
3428
+ },
3348
3429
  }, async (args) => {
3349
3430
  return await handleNpmQuality(args);
3350
3431
  });
3351
- server.tool('npmMaintenance', 'Analyze package maintenance metrics', {
3352
- packages: z.array(z.string()).describe('List of package names to analyze'),
3353
- }, {
3354
- title: 'Analyze NPM Package Maintenance (NPMS.io)',
3355
- readOnlyHint: true,
3356
- openWorldHint: true,
3357
- idempotentHint: true, // Score for a version is stable, for 'latest' can change
3432
+ server.registerTool('npmMaintenance', {
3433
+ description: 'Analyze package maintenance metrics',
3434
+ inputSchema: {
3435
+ packages: z.array(z.string()).describe('List of package names to analyze'),
3436
+ },
3437
+ annotations: {
3438
+ title: 'Analyze NPM Package Maintenance (NPMS.io)',
3439
+ readOnlyHint: true,
3440
+ openWorldHint: true,
3441
+ idempotentHint: true, // Score for a version is stable, for 'latest' can change
3442
+ },
3358
3443
  }, async (args) => {
3359
3444
  return await handleNpmMaintenance(args);
3360
3445
  });
@@ -3387,8 +3472,11 @@ async function main() {
3387
3472
  // Type guard for NpmPackageVersionSchema
3388
3473
  function isNpmPackageVersionData(data) {
3389
3474
  try {
3390
- // Use safeParse for type guards to avoid throwing errors on invalid data
3391
- return NpmPackageVersionSchema.safeParse(data).success;
3475
+ const result = NpmPackageVersionSchema.safeParse(data);
3476
+ if (!result.success) {
3477
+ console.error('isNpmPackageVersionData validation failed:', JSON.stringify(result.error.issues, null, 2));
3478
+ }
3479
+ return result.success;
3392
3480
  }
3393
3481
  catch (e) {
3394
3482
  // This catch block might not be strictly necessary with safeParse but kept for safety