Skip to main content
Glama
Sadaf987

School Attendance MCP Server

by Sadaf987
github.service.ts12.2 kB
import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import type { Octokit } from '@octokit/rest'; @Injectable() export class GithubService { private octokit: Octokit | null = null; private octokitPromise: Promise<Octokit> | null = null; constructor(private configService?: ConfigService) {} private async getOctokit(): Promise<Octokit> { if (this.octokit) { return this.octokit; } if (this.octokitPromise) { return this.octokitPromise; } this.octokitPromise = (async () => { try { // Dynamic import for ES module const { Octokit } = await import('@octokit/rest'); // Get token from environment or use a default one (limited to public repos) let token = ''; if (this.configService) { token = (this.configService.get<string>('GITHUB_PERSONAL_ACCESS_TOKEN') || '').trim(); } else if (process.env.GITHUB_PERSONAL_ACCESS_TOKEN) { token = process.env.GITHUB_PERSONAL_ACCESS_TOKEN.trim(); } // Remove 'Bearer ' prefix if present (Octokit adds it automatically) token = token.replace(/^Bearer\s+/i, ''); console.log('Initializing Octokit with token:', token ? '***' + token.slice(-4) : 'No token (public access only)'); this.octokit = new Octokit({ auth: token || undefined, // Use undefined instead of empty string request: { timeout: 10000 // 10 second timeout } }); // Test the connection await this.octokit.rest.users.getAuthenticated().catch(() => { console.log('GitHub authentication failed. Continuing with unauthenticated access (rate limits will apply).'); }); return this.octokit; } catch (error) { console.error('Error initializing Octokit:', error); throw new Error(`Failed to initialize GitHub client: ${error instanceof Error ? error.message : String(error)}`); } })(); return this.octokitPromise; } async commitChanges({ repoOwner, repoName, branchName, commitMessage, filesToCommit, }: { repoOwner: string; repoName: string; branchName: string; commitMessage: string; filesToCommit: Array<{ path: string; content: string }>; }) { try { const octokit = await this.getOctokit(); // Creating or updating files on GitHub if (!filesToCommit || filesToCommit.length === 0) { throw new Error('No files to commit'); } const file = filesToCommit[0]; if (!file) { throw new Error('No files to commit'); } // Check if file exists first to get SHA (required for updates) let sha: string | undefined; try { const existingFile = await octokit.repos.getContent({ owner: repoOwner, repo: repoName, path: file.path, ref: branchName, }); if (Array.isArray(existingFile.data)) { throw new Error('Path is a directory, not a file'); } sha = existingFile.data.sha; } catch (error: any) { // File doesn't exist yet, that's okay - we'll create it if (error.status !== 404) { throw error; } } const response = await octokit.repos.createOrUpdateFileContents({ owner: repoOwner, repo: repoName, path: file.path, message: commitMessage, content: Buffer.from(file.content).toString('base64'), branch: branchName, ...(sha && { sha }), // Include SHA if file exists (required for updates) }); return { content: [ { type: 'text', text: `Committed changes to ${repoName} on branch ${branchName}: ${commitMessage}`, }, ], }; } catch (error) { console.error('Error committing changes:', error); const message = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `Failed to commit changes: ${message}`, }, ], }; } } async getRepoInfo({ repoOwner, repoName, }: { repoOwner: string; repoName: string; }) { try { console.log(`Fetching repository info for ${repoOwner}/${repoName}`); const octokit = await this.getOctokit(); // Fetch repository info using GitHub's REST API console.log('Sending request to GitHub API...'); const response = await octokit.rest.repos.get({ owner: repoOwner, repo: repoName, mediaType: { format: 'raw', }, }).catch(error => { console.error('GitHub API error:', error); throw error; }); console.log('Received response from GitHub API'); return { content: [ { type: 'text', text: `Repository Info: Name: ${response.data.name} Owner: ${response.data.owner?.login || 'Unknown'} Default Branch: ${response.data.default_branch} Description: ${response.data.description || 'No description'} Stars: ${response.data.stargazers_count} Forks: ${response.data.forks_count} Private: ${response.data.private} URL: ${response.data.html_url}`, }, ], }; } catch (error) { console.error('Error in getRepoInfo:', error); let errorMessage = 'Unknown error occurred'; if (error.status === 404) { errorMessage = `Repository '${repoOwner}/${repoName}' not found or access denied`; } else if (error.status === 403) { errorMessage = 'GitHub API rate limit exceeded. Please set GITHUB_PERSONAL_ACCESS_TOKEN in your .env file.'; } else if (error.response) { errorMessage = `GitHub API error: ${error.response.data?.message || error.message}`; } else if (error instanceof Error) { errorMessage = error.message; } else if (typeof error === 'string') { errorMessage = error; } return { content: [ { type: 'text', text: `Failed to fetch repository info: ${errorMessage}`, }, ], }; } } async createBranch({ repoOwner, repoName, branchName, baseBranch, }: { repoOwner: string; repoName: string; branchName: string; baseBranch?: string; }) { try { const octokit = await this.getOctokit(); // Get the SHA of the base branch (or default branch if not specified) const baseBranchName = baseBranch || 'main'; const refResponse = await octokit.git.getRef({ owner: repoOwner, repo: repoName, ref: `heads/${baseBranchName}`, }); const baseSha = refResponse.data.object.sha; // Create the new branch await octokit.git.createRef({ owner: repoOwner, repo: repoName, ref: `refs/heads/${branchName}`, sha: baseSha, }); return { content: [ { type: 'text', text: `Successfully created branch '${branchName}' from '${baseBranchName}' in repository ${repoOwner}/${repoName}`, }, ], }; } catch (error) { console.error('Error creating branch:', error); const message = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `Failed to create branch: ${message}`, }, ], }; } } async listBranches({ repoOwner, repoName, }: { repoOwner: string; repoName: string; }) { try { const octokit = await this.getOctokit(); // Fetch all branches using GitHub's REST API const response = await octokit.repos.listBranches({ owner: repoOwner, repo: repoName, }); const branches = response.data.map((branch) => ({ name: branch.name, protected: branch.protected || false, sha: branch.commit.sha, })); const branchList = branches .map((b) => `- ${b.name}${b.protected ? ' (protected)' : ''} (${b.sha.substring(0, 7)})`) .join('\n'); return { content: [ { type: 'text', text: `Branches in ${repoOwner}/${repoName}:\n${branchList}\n\nTotal: ${branches.length} branch${branches.length !== 1 ? 'es' : ''}`, }, ], }; } catch (error) { console.error('Error listing branches:', error); const message = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `Failed to list branches: ${message}`, }, ], }; } } async getFileContents({ repoOwner, repoName, filePath, branch, }: { repoOwner: string; repoName: string; filePath: string; branch?: string; }) { try { const octokit = await this.getOctokit(); const response = await octokit.repos.getContent({ owner: repoOwner, repo: repoName, path: filePath, ...(branch && { ref: branch }), }); if (Array.isArray(response.data)) { return { content: [ { type: 'text', text: `Path "${filePath}" is a directory, not a file. Use list_files to see directory contents.`, }, ], }; } // Check if it's a file (not symlink or submodule) if (response.data.type !== 'file' || !('content' in response.data)) { return { content: [ { type: 'text', text: `Path "${filePath}" is not a regular file (type: ${response.data.type}).`, }, ], }; } // Decode base64 content const content = Buffer.from(response.data.content, 'base64').toString('utf-8'); return { content: [ { type: 'text', text: `File: ${filePath}\nSize: ${response.data.size} bytes\nSHA: ${response.data.sha}\n\nContent:\n${content}`, }, ], }; } catch (error) { console.error('Error fetching file contents:', error); const message = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `Failed to fetch file contents: ${message}`, }, ], }; } } async listCommits({ repoOwner, repoName, branch, perPage, }: { repoOwner: string; repoName: string; branch?: string; perPage?: number; }) { try { const octokit = await this.getOctokit(); const response = await octokit.repos.listCommits({ owner: repoOwner, repo: repoName, ...(branch && { sha: branch }), per_page: perPage || 10, }); const commits = response.data.map((commit) => ({ sha: commit.sha.substring(0, 7), message: commit.commit.message.split('\n')[0], // First line only author: commit.commit.author?.name || 'Unknown', date: commit.commit.author?.date || 'Unknown', url: commit.html_url, })); const commitList = commits .map( (c) => `- ${c.sha} - ${c.message}\n Author: ${c.author} | Date: ${c.date}\n URL: ${c.url}`, ) .join('\n\n'); return { content: [ { type: 'text', text: `Recent commits in ${repoOwner}/${repoName}${branch ? ` (branch: ${branch})` : ''}:\n\n${commitList}\n\nTotal: ${commits.length} commit${commits.length !== 1 ? 's' : ''} shown`, }, ], }; } catch (error) { console.error('Error listing commits:', error); const message = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `Failed to list commits: ${message}`, }, ], }; } } }

Latest Blog Posts

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/Sadaf987/github_sdk'

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