Skip to main content
Glama

mcp-github-project-manager

GitHubMilestoneRepository.ts6.47 kB
import { BaseGitHubRepository } from "./BaseRepository"; import { Milestone, CreateMilestone, MilestoneRepository, MilestoneId, Issue } from "../../../domain/types"; import { ResourceType, ResourceStatus } from "../../../domain/resource-types"; import { GitHubIssueRepository } from "./GitHubIssueRepository"; interface GitHubMilestone { id: string; number: number; title: string; description: string | null; dueOn: string | null; state: "open" | "closed"; createdAt: string; updatedAt: string; closedAt: string | null; progress: { enabled: boolean; openIssues: number; closedIssues: number; completionPercentage: number; }; } interface CreateMilestoneResponse { createMilestone: { milestone: GitHubMilestone; }; } interface UpdateMilestoneResponse { updateMilestone: { milestone: GitHubMilestone; }; } interface GetMilestoneResponse { repository: { milestone: GitHubMilestone | null; }; } interface ListMilestonesResponse { repository: { milestones: { nodes: GitHubMilestone[]; }; }; } export class GitHubMilestoneRepository extends BaseGitHubRepository implements MilestoneRepository { private readonly factory: any; constructor(octokit: any, config: any) { super(octokit, config); // We need to add a factory field to the class in order to create other repositories this.factory = { createIssueRepository: () => { return new GitHubIssueRepository(octokit, config); } }; } private mapGitHubMilestoneToMilestone(githubMilestone: GitHubMilestone): Milestone { return { id: githubMilestone.id, number: parseInt(githubMilestone.number.toString()), title: githubMilestone.title, description: githubMilestone.description || "", dueDate: githubMilestone.dueOn || undefined, status: githubMilestone.state === "open" ? ResourceStatus.ACTIVE : ResourceStatus.CLOSED, progress: { percent: githubMilestone.progress?.completionPercentage || 0, complete: githubMilestone.progress?.closedIssues || 0, total: (githubMilestone.progress?.openIssues || 0) + (githubMilestone.progress?.closedIssues || 0) }, createdAt: githubMilestone.createdAt, updatedAt: githubMilestone.updatedAt, url: `https://github.com/${this.owner}/${this.repo}/milestone/${githubMilestone.number}` }; } private mapRestMilestoneToMilestone(restMilestone: any): Milestone { return { id: restMilestone.node_id, number: restMilestone.number, title: restMilestone.title, description: restMilestone.description || "", dueDate: restMilestone.due_on || undefined, status: restMilestone.state === "open" ? ResourceStatus.ACTIVE : ResourceStatus.CLOSED, progress: { percent: 0, // REST API doesn't provide progress info complete: restMilestone.closed_issues || 0, total: (restMilestone.open_issues || 0) + (restMilestone.closed_issues || 0) }, createdAt: restMilestone.created_at, updatedAt: restMilestone.updated_at, url: `https://github.com/${this.owner}/${this.repo}/milestone/${restMilestone.number}` }; } async create(data: CreateMilestone): Promise<Milestone> { // Use REST API for milestone creation since GraphQL doesn't support it const response = await this.rest( (params) => this.octokit.rest.issues.createMilestone(params), { title: data.title, description: data.description, due_on: data.dueDate, state: 'open' } ); return this.mapRestMilestoneToMilestone(response); } async update(id: MilestoneId, data: Partial<Milestone>): Promise<Milestone> { // Use REST API for milestone updates since GraphQL doesn't support it const response = await this.rest( (params) => this.octokit.rest.issues.updateMilestone(params), { milestone_number: parseInt(id), title: data.title, description: data.description, due_on: data.dueDate, state: data.status === ResourceStatus.CLOSED ? "closed" : "open", } ); return this.mapRestMilestoneToMilestone(response); } async delete(id: MilestoneId): Promise<void> { // Use REST API for milestone deletion since GraphQL doesn't support it await this.rest( (params) => this.octokit.rest.issues.deleteMilestone(params), { milestone_number: parseInt(id) } ); } async findById(id: MilestoneId): Promise<Milestone | null> { const query = ` query($owner: String!, $repo: String!, $number: Int!) { repository(owner: $owner, name: $repo) { milestone(number: $number) { id number title description dueOn state createdAt updatedAt } } } `; const response = await this.graphql<GetMilestoneResponse>(query, { owner: this.owner, repo: this.repo, number: parseInt(id), }); const milestone = response.repository.milestone; if (!milestone) return null; return this.mapGitHubMilestoneToMilestone(milestone); } async findAll(): Promise<Milestone[]> { const query = ` query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { milestones(first: 100) { nodes { id number title description dueOn state createdAt updatedAt } } } } `; const response = await this.graphql<ListMilestonesResponse>(query, { owner: this.owner, repo: this.repo, }); return response.repository.milestones.nodes.map(milestone => this.mapGitHubMilestoneToMilestone(milestone) ); } async findByDueDate(before: Date): Promise<Milestone[]> { const all = await this.findAll(); return all.filter(milestone => { if (!milestone.dueDate) return false; return new Date(milestone.dueDate) <= before; }); } async getOverdue(): Promise<Milestone[]> { return this.findByDueDate(new Date()); } async getIssues(id: MilestoneId): Promise<Issue[]> { // We can leverage the IssueRepository's findByMilestone method const issueRepo = this.factory.createIssueRepository(); return issueRepo.findByMilestone(id); } }

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/kunwarVivek/mcp-github-project-manager'

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