UNPKG

mcp-package-version

Version:

An MCP server to provide LLMs the latest (stable) version of packages in package.json and requirements.txt files

155 lines (154 loc) 7 kB
import axios from 'axios'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; export class GitHubActionsHandler { githubApiUrl = 'https://api.github.com'; githubToken = process.env.GITHUB_TOKEN || ''; getHeaders() { const headers = { 'Accept': 'application/vnd.github.v3+json' }; if (this.githubToken) { headers['Authorization'] = `token ${this.githubToken}`; } return headers; } async getLatestActionVersion(action) { try { const { owner, repo, currentVersion } = action; const headers = this.getHeaders(); // First, try to get releases const releasesUrl = `${this.githubApiUrl}/repos/${owner}/${repo}/releases`; const releasesResponse = await axios.get(releasesUrl, { headers }); if (releasesResponse.data && releasesResponse.data.length > 0) { // Sort releases by published date (newest first) const releases = releasesResponse.data.sort((a, b) => new Date(b.published_at).getTime() - new Date(a.published_at).getTime()); // Get the latest release const latestRelease = releases[0]; // Extract version from tag name (remove 'v' prefix if present) const latestVersion = latestRelease.tag_name.replace(/^v/, ''); // Parse version components const versionParts = latestVersion.split('.'); const latestMajorVersion = versionParts[0] ? `${versionParts[0]}` : undefined; const latestMinorVersion = versionParts[1] ? `${versionParts[0]}.${versionParts[1]}` : undefined; const latestPatchVersion = latestVersion; return { name: `${owner}/${repo}`, owner, repo, currentVersion, latestVersion, latestMajorVersion, latestMinorVersion, latestPatchVersion, publishedAt: latestRelease.published_at, url: latestRelease.html_url }; } // If no releases, try to get tags const tagsUrl = `${this.githubApiUrl}/repos/${owner}/${repo}/tags`; const tagsResponse = await axios.get(tagsUrl, { headers }); if (tagsResponse.data && tagsResponse.data.length > 0) { // Get the first tag (usually the latest) const latestTag = tagsResponse.data[0]; // Extract version from tag name (remove 'v' prefix if present) const latestVersion = latestTag.name.replace(/^v/, ''); // Parse version components const versionParts = latestVersion.split('.'); const latestMajorVersion = versionParts[0] ? `${versionParts[0]}` : undefined; const latestMinorVersion = versionParts[1] ? `${versionParts[0]}.${versionParts[1]}` : undefined; const latestPatchVersion = latestVersion; return { name: `${owner}/${repo}`, owner, repo, currentVersion, latestVersion, latestMajorVersion, latestMinorVersion, latestPatchVersion, url: latestTag.commit.url }; } // If no releases or tags, check if the repo exists const repoUrl = `${this.githubApiUrl}/repos/${owner}/${repo}`; await axios.get(repoUrl, { headers }); // If we get here, the repo exists but has no releases or tags return { name: `${owner}/${repo}`, owner, repo, currentVersion, latestVersion: 'unknown', url: `https://github.com/${owner}/${repo}` }; } catch (error) { console.error(`Error fetching GitHub Action ${action.owner}/${action.repo}:`, error); // Check if it's a 404 error (repo not found) if (error.response && error.response.status === 404) { return { name: `${action.owner}/${action.repo}`, owner: action.owner, repo: action.repo, currentVersion: action.currentVersion, latestVersion: 'not found', skipped: true, skipReason: 'Repository not found' }; } // Check if it's a rate limit error if (error.response && error.response.status === 403 && error.response.headers['x-ratelimit-remaining'] === '0') { return { name: `${action.owner}/${action.repo}`, owner: action.owner, repo: action.repo, currentVersion: action.currentVersion, latestVersion: 'unknown', skipped: true, skipReason: 'GitHub API rate limit exceeded' }; } // Other errors return { name: `${action.owner}/${action.repo}`, owner: action.owner, repo: action.repo, currentVersion: action.currentVersion, latestVersion: 'error', skipped: true, skipReason: `Error: ${error.message || 'Unknown error'}` }; } } async getLatestVersion(args) { if (!args.actions || !Array.isArray(args.actions) || args.actions.length === 0) { throw new McpError(ErrorCode.InvalidParams, 'At least one GitHub Action must be provided'); } try { const results = []; // Process each action in parallel const promises = args.actions.map(action => this.getLatestActionVersion(action)); const actionVersions = await Promise.all(promises); results.push(...actionVersions); // Filter out unnecessary details if includeDetails is false if (args.includeDetails !== true) { results.forEach(result => { delete result.publishedAt; delete result.url; }); } return { content: [ { type: 'text', text: JSON.stringify(results, null, 2), }, ], }; } catch (error) { console.error('Error checking GitHub Actions:', error); throw new McpError(ErrorCode.InternalError, `Failed to fetch GitHub Actions information: ${error.message || 'Unknown error'}`); } } }