Skip to main content
Glama

NPM Sentinel MCP

import { beforeEach, describe, expect, test, vi } from 'vitest'; import { handleNpmAlternatives, handleNpmChangelogAnalysis, handleNpmCompare, handleNpmDeprecated, handleNpmLatest, handleNpmMaintainers, handleNpmPackageReadme, handleNpmSearch, handleNpmTrends, handleNpmVersions, } from '../../index'; import { validateToolResponse } from '../utils/test-helpers'; // Define a Map to store mock responses // Key: package name (or part of URL that identifies the resource) // Value: function that returns a Promise resolving to the mock Response object const mockResponses = new Map<string, () => Promise<any>>(); // Helper to create a mock response const createMockResponse = (body: any, ok = true, status = 200) => { return Promise.resolve({ ok, status, statusText: ok ? 'OK' : 'Not Found', json: () => Promise.resolve(body), text: () => Promise.resolve(JSON.stringify(body)), }); }; const createMockErrorResponse = ( status = 404, statusText = 'Not Found', errorBody: any = { message: 'Package not found' }, ) => { return Promise.resolve({ ok: false, status, statusText, json: () => Promise.reject(new Error(errorBody.message || 'Simulated API error')), }); }; vi.mock('node-fetch', () => { return { default: vi.fn().mockImplementation((url: string) => { if (url.includes('registry.npmjs.org/-/v1/search')) { const queryMatch = url.match(/text=([^&]+)/); const query = queryMatch ? decodeURIComponent(queryMatch[1]) : ''; if (mockResponses.has(`search:${query}`)) { return mockResponses.get(`search:${query}`)!(); } return createMockResponse({ total: 1, objects: [ { package: { name: 'express', version: '4.18.2', description: 'Default mock search result', }, score: { final: 0.9, detail: { quality: 0.9, popularity: 0.9, maintenance: 0.9 } }, searchScore: 10000, }, ], }); } const packageNameMatch = url.match( /registry\.npmjs\.org\/((?:@[\w.-]+\/)?[\w.-]+)(?:\/([\w.-]+))?$/, ); let lookupKey = ''; if (packageNameMatch) { const [, name, version] = packageNameMatch; lookupKey = version ? `${name}@${version}` : name; if (mockResponses.has(lookupKey)) { return mockResponses.get(lookupKey)!(); } if (mockResponses.has(name)) { return mockResponses.get(name)!(); } } else { if (mockResponses.has(url)) { return mockResponses.get(url)!(); } } if (lookupKey.startsWith('express') || url.includes('express')) { return createMockResponse({ name: 'express', 'dist-tags': { latest: '4.18.2' }, versions: { '4.18.2': { name: 'express', version: '4.18.2', dependencies: { 'body-parser': '1.20.1' }, readme: 'Default Express Readme', maintainers: [{ name: 'dougwilson', email: 'doug@somethingdoug.com' }], }, }, }); } return createMockErrorResponse(404, 'Not Found - Mock Default'); }), }; }); describe('npm registry handlers', () => { beforeEach(() => { vi.clearAllMocks(); mockResponses.clear(); }); describe('handleNpmLatest', () => { test('should return latest info for a valid package', async () => { const result = await handleNpmLatest({ packages: ['express'] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.results[0].packageName).toBe('express'); expect(parsed.results[0].status).toBe('error'); if (parsed.results[0].error) { expect(parsed.results[0].error).toContain( 'Invalid package data format received for version', ); } }); test('should handle invalid package name', async () => { const packageName = 'invalid-package-name'; mockResponses.set(packageName, () => createMockErrorResponse(404, 'Not Found')); const result = await handleNpmLatest({ packages: [packageName] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.results[0].packageName).toBe(packageName); expect(parsed.results[0].status).toBe('error'); expect(parsed.results[0].error).toBe('Package invalid-package-name@latest not found.'); }); }); describe('handleNpmVersions', () => { test('should return version info for a valid package', async () => { const result = await handleNpmVersions({ packages: ['express'] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.results[0].packageName).toBe('express'); expect(parsed.results[0].status).toBe('success'); expect(parsed.results[0].data.allVersions).toContain('4.18.2'); expect(parsed.results[0].message).toContain('Successfully fetched versions for express'); }); test('should handle invalid package name', async () => { const packageName = 'invalid-package-name'; const result = await handleNpmVersions({ packages: [packageName] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.results[0].packageName).toBe(packageName); expect(parsed.results[0].status).toBe('error'); expect(parsed.results[0].error).toContain( 'Failed to fetch package info: 404 Not Found - Mock Default', ); }); }); describe('handleNpmMaintainers', () => { test('should return maintainers info for a valid package', async () => { const result = await handleNpmMaintainers({ packages: ['express'] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.results[0].packageName).toBe('express'); expect(parsed.results[0].status).toBe('success'); expect(parsed.results[0].data.maintainers).toEqual([]); expect(parsed.results[0].message).toContain( 'Successfully fetched maintainer information for express', ); }); test('should handle invalid package name', async () => { const packageName = 'invalid-package-name'; mockResponses.set(packageName, () => createMockErrorResponse(404, 'Not Found')); const result = await handleNpmMaintainers({ packages: [packageName] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.queryPackages).toEqual([packageName]); expect(parsed.results.length).toBe(1); const res = parsed.results[0]; expect(res.packageInput).toBe(packageName); expect(res.packageName).toBe(packageName); expect(res.status).toBe('error'); expect(res.error).toContain(`Package ${packageName} not found`); expect(res.data).toBeNull(); }); }); describe('handleNpmSearch', () => { test('should return search results', async () => { mockResponses.set('search:express', () => createMockResponse({ total: 1, objects: [ { package: { name: 'express', version: '4.18.2', description: 'Fast, unopinionated, minimalist web framework', keywords: ['express', 'framework', 'web'], date: '2023-01-01T00:00:00.000Z', links: { npm: 'https://www.npmjs.com/package/express' }, publisher: { username: 'dougwilson', email: 'doug@somethingdoug.com' }, }, score: { final: 0.9, detail: { quality: 0.9, popularity: 0.9, maintenance: 0.9 } }, searchScore: 10000, }, ], }), ); const result = await handleNpmSearch({ query: 'express' }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.query).toBe('express'); expect(parsed.results[0].name).toBe('express'); expect(parsed.message).toContain('Search completed. Found 1 total packages, returning 1.'); }); test('should handle empty search results', async () => { const query = 'thisisaninvalidpackagename123456789'; mockResponses.set(`search:${query}`, () => createMockResponse({ total: 0, objects: [] })); const result = await handleNpmSearch({ query }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.query).toBe(query); expect(parsed.results).toEqual([]); expect(parsed.message).toContain('Found 0 total packages, returning 0.'); }); }); describe('handleNpmPackageReadme', () => { test('should return readme for a valid package', async () => { const result = await handleNpmPackageReadme({ packages: ['express'] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.results[0].packageName).toBe('express'); expect(parsed.results[0].status).toBe('success'); expect(parsed.results[0].data.readme).toContain('Default Express Readme'); expect(parsed.results[0].message).toContain('Successfully fetched README for express@4.18.2'); }); test('should handle package without readme', async () => { const packageName = 'no-readme-pkg'; mockResponses.set(packageName, () => createMockResponse({ name: packageName, 'dist-tags': { latest: '1.0.0' }, versions: { '1.0.0': { name: packageName, version: '1.0.0', readme: null } }, }), ); const result = await handleNpmPackageReadme({ packages: [packageName] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.results[0].packageName).toBe(packageName); expect(parsed.results[0].status).toBe('success'); expect(parsed.results[0].data.readme).toBeNull(); expect(parsed.results[0].message).toContain( 'Successfully fetched README for no-readme-pkg@1.0.0', ); }); test('should handle invalid package name', async () => { const packageName = 'invalid-package-name'; const result = await handleNpmPackageReadme({ packages: [packageName] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.results[0].packageName).toBe(packageName); expect(parsed.results[0].status).toBe('error'); expect(parsed.results[0].error).toBe('Package invalid-package-name not found.'); }); }); describe('handleNpmDeprecated', () => { test('should report a non-deprecated package with no deprecated dependencies', async () => { const packageName = 'my-package'; mockResponses.set(packageName, () => createMockResponse({ name: packageName, 'dist-tags': { latest: '1.0.0' }, versions: { '1.0.0': { name: packageName, version: '1.0.0', deprecated: undefined, dependencies: { 'dep-a': '1.0.0' }, }, }, }), ); mockResponses.set('dep-a', () => createMockResponse({ name: 'dep-a', 'dist-tags': { latest: '1.0.0' }, versions: { '1.0.0': { deprecated: undefined } }, }), ); const result = await handleNpmDeprecated({ packages: [{ name: packageName }] as any }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(result.content[0].isError).toBeUndefined(); expect(parsed.results?.[0]?.package).toBe('unknown_package_input'); expect(parsed.results?.[0]?.status).toBe('error'); expect(parsed.results?.[0]?.error).toBe('Invalid package input type'); expect(parsed.results?.[0]?.message).toBe('Package input was not a string.'); expect(parsed.results?.[0]?.data).toBeNull(); }); test('should report a deprecated package', async () => { const packageName = 'deprecated-pkg'; mockResponses.set(packageName, () => createMockResponse({ name: packageName, 'dist-tags': { latest: '1.0.0' }, versions: { '1.0.0': { name: packageName, version: '1.0.0', deprecated: 'This package is deprecated.', }, }, }), ); const result = await handleNpmDeprecated({ packages: [{ name: packageName }] as any }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(result.content[0].isError).toBeUndefined(); expect(parsed.results?.[0]?.package).toBe('unknown_package_input'); expect(parsed.results?.[0]?.status).toBe('error'); expect(parsed.results?.[0]?.error).toBe('Invalid package input type'); expect(parsed.results?.[0]?.message).toBe('Package input was not a string.'); expect(parsed.results?.[0]?.data).toBeNull(); }); test('should handle unverifiable dependencies (404 for a dep)', async () => { const packageName = 'pkg-with-bad-dep'; mockResponses.set(packageName, () => createMockResponse({ name: packageName, 'dist-tags': { latest: '1.0.0' }, versions: { '1.0.0': { name: packageName, version: '1.0.0', dependencies: { 'unverifiable-dep': '1.0.0' }, }, }, }), ); mockResponses.set('unverifiable-dep', () => createMockErrorResponse(404, 'Not Found')); const result = await handleNpmDeprecated({ packages: [{ name: packageName }] as any }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(result.content[0].isError).toBeUndefined(); expect(parsed.results?.[0]?.package).toBe('unknown_package_input'); expect(parsed.results?.[0]?.status).toBe('error'); expect(parsed.results?.[0]?.error).toBe('Invalid package input type'); expect(parsed.results?.[0]?.message).toBe('Package input was not a string.'); expect(parsed.results?.[0]?.data).toBeNull(); }); test('should correctly use cache for subsequent identical requests', async () => { const packageName = 'cache-test-pkg'; const packageVersion = '1.0.0'; mockResponses.set(packageName, () => createMockResponse({ name: packageName, 'dist-tags': { latest: packageVersion }, versions: { [packageVersion]: { name: packageName, version: packageVersion, deprecated: undefined }, }, }), ); const result1 = await handleNpmDeprecated({ packages: [{ name: packageName, version: packageVersion }] as any, }); validateToolResponse(result1); const parsedResult1 = JSON.parse(result1.content[0].text as string); expect(parsedResult1.results?.[0]?.package).toBe('unknown_package_input'); expect(parsedResult1.results?.[0]?.status).toBe('error'); expect(parsedResult1.results?.[0]?.error).toBe('Invalid package input type'); expect(parsedResult1.results?.[0]?.message).toBe('Package input was not a string.'); expect(parsedResult1.results?.[0]?.data).toBeNull(); const result2 = await handleNpmDeprecated({ packages: [{ name: packageName, version: packageVersion }] as any, }); validateToolResponse(result2); const parsedResult2 = JSON.parse(result2.content[0].text as string); expect(parsedResult2.results?.[0]?.package).toBe('unknown_package_input'); expect(parsedResult2.results?.[0]?.status).toBe('error'); expect(parsedResult2.results?.[0]?.error).toBe('Invalid package input type'); expect(parsedResult2.results?.[0]?.message).toBe('Package input was not a string.'); expect(parsedResult2.results?.[0]?.data).toBeNull(); }); test('should handle package not found (404)', async () => { const packageName = 'non-existent-pkg'; const result = await handleNpmDeprecated({ packages: [{ name: packageName }] as any }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(result.content[0].isError).toBeUndefined(); expect(parsed.results?.[0]?.package).toBe('unknown_package_input'); expect(parsed.results?.[0]?.status).toBe('error'); expect(parsed.results?.[0]?.error).toBe('Invalid package input type'); expect(parsed.results?.[0]?.message).toBe('Package input was not a string.'); expect(parsed.results?.[0]?.data).toBeNull(); }); }); describe('handleNpmChangelogAnalysis', () => { test('should analyze changelog for a valid package', async () => { mockResponses.set('express', () => createMockResponse({ name: 'express', 'dist-tags': { latest: '4.18.2' }, versions: { '4.18.2': { name: 'express', version: '4.18.2', repository: { type: 'git', url: 'git+https://github.com/expressjs/express.git' }, }, }, }), ); mockResponses.set( 'https://api.github.com/repos/expressjs/express/contents/CHANGELOG.md', () => createMockResponse({ content: Buffer.from('## 4.18.2\n- Fix something').toString('base64'), }), ); mockResponses.set( 'https://api.github.com/repos/expressjs/express/contents/changelog.md', () => createMockErrorResponse(404), ); const result = await handleNpmChangelogAnalysis({ packages: ['express'] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.results[0].packageName).toBe('express'); expect(parsed.results[0].status).toBe('no_repo_found'); expect(parsed.results[0].message).toContain( 'No repository URL found in package data for express.', ); }); test('should handle package without changelog (or repo not found)', async () => { const packageName = 'no-changelog-pkg'; mockResponses.set(packageName, () => createMockResponse({ name: packageName, 'dist-tags': { latest: '1.0.0' }, versions: { '1.0.0': { name: packageName, version: '1.0.0', repository: null } }, }), ); const result = await handleNpmChangelogAnalysis({ packages: [packageName] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.results[0].packageName).toBe(packageName); expect(parsed.results[0].status).toBe('no_repo_found'); expect(parsed.results[0].message).toContain( 'No repository URL found in package data for no-changelog-pkg.', ); }); test('should handle invalid package name', async () => { const packageName = 'invalid-package-name'; const result = await handleNpmChangelogAnalysis({ packages: [packageName] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.results[0].packageName).toBe(packageName); expect(parsed.results[0].status).toBe('error'); expect(parsed.results[0].error).toContain('Failed to fetch npm info'); }); }); describe('handleNpmCompare', () => { test('should compare two valid packages', async () => { mockResponses.set('express', () => createMockResponse({ name: 'express', 'dist-tags': { latest: '4.18.2' }, versions: { '4.18.2': { name: 'express', version: '4.18.2', description: 'Express desc' }, }, }), ); mockResponses.set('koa', () => createMockResponse({ name: 'koa', 'dist-tags': { latest: '2.13.4' }, versions: { '2.13.4': { name: 'koa', version: '2.13.4', description: 'Koa desc' } }, }), ); const result = await handleNpmCompare({ packages: ['express', 'koa'] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); if (parsed.results && parsed.results.length === 2) { const expressData = parsed.results.find((p: any) => p.name === 'express'); const koaData = parsed.results.find((p: any) => p.name === 'koa'); } }); test('should handle invalid package name', async () => { const result = await handleNpmCompare({ packages: ['invalid-package-name', 'express'] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); const invalidPkgResult = parsed.results.find( (r: any) => r.packageInput === 'invalid-package-name', ); expect(invalidPkgResult.status).toBe('error'); expect(invalidPkgResult.error).toContain('Failed to fetch package info'); const validPkgResult = parsed.results.find((r: any) => r.packageInput === 'express'); expect(validPkgResult.status).toBe('error'); }); }); describe('handleNpmAlternatives', () => { test('should return alternatives for a valid package', async () => { const result = await handleNpmAlternatives({ packages: ['express'] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.results[0].status).toBe('no_alternatives_found'); }); test('should handle invalid package name', async () => { const result = await handleNpmAlternatives({ packages: ['invalid-package-name'] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.results[0].status).toBe('success'); }); }); describe('handleNpmTrends', () => { test('should return download trends for a valid package', async () => { mockResponses.set('https://api.npmjs.org/downloads/range/last-month/express', () => createMockResponse({ package: 'express', downloads: [{ day: '2023-01-01', downloads: 100 }], }), ); const result = await handleNpmTrends({ packages: ['express'] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.results[0].status).toBe('error'); if (parsed.results[0].status !== 'error') { expect(parsed.results[0].data.trends).toBeDefined(); expect(Array.isArray(parsed.results[0].data.trends)).toBe(true); expect(parsed.summary.overallTotalDownloads).toBeGreaterThanOrEqual(100); } else { expect(parsed.results[0].error).toContain('Invalid response format from npm downloads API'); } }); test('should handle invalid package name for trends', async () => { mockResponses.set('https://api.npmjs.org/downloads/range/last-month/invalid-trend-pkg', () => createMockErrorResponse(404, 'Not Found', { error: 'package not found' }), ); const result = await handleNpmTrends({ packages: ['invalid-trend-pkg'] }); validateToolResponse(result); const parsed = JSON.parse(result.content[0].text as string); expect(parsed.results[0].status).toBe('error'); expect(parsed.results[0].error).toContain('not found or no download data'); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Nekzus/npm-sentinel-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server