GitHub Integration MCP Server Recipe

An MCP server that connects AI assistants to GitHub — list repos, read issues, create PRs, and manage branches through the GitHub API.

IntermediatePrep: 5 minCook: 25 minServes: GitHub workflow automation via AI

title: "GitHub Integration MCP Server Recipe" description: "An MCP server that connects AI assistants to GitHub — list repos, read issues, create PRs, and manage branches through the GitHub API." order: 4 keywords:

  • mcp github server
  • mcp-framework github
  • github api mcp
  • ai github integration date: "2026-04-01" difficulty: "Intermediate" prepTime: "5 min" cookTime: "25 min" serves: "GitHub workflow automation via AI"

Prep Time

5 minutes — scaffold and get a GitHub personal access token.

Cook Time

25 minutes — implement repo, issue, and PR tools.

Serves

Any AI assistant that needs to interact with GitHub repositories. Perfect for code review workflows, issue triage, and automated PR creation.

Ingredients

  • Node.js 18+
  • mcp-framework (3.3M+ downloads)
  • GitHub Personal Access Token
  • TypeScript

Instructions

Step 1: Scaffold and Configure

npx mcp-framework create github-server
cd github-server

Step 2: Create the List Repos Tool

Create src/tools/ListReposTool.ts:

import { MCPTool } from "mcp-framework";
import { z } from "zod";

const GITHUB_TOKEN = process.env.GITHUB_TOKEN || "";

class ListReposTool extends MCPTool<typeof inputSchema> {
  name = "list_repos";
  description = "List GitHub repositories for a user or organization";

  schema = {
    owner: {
      type: z.string(),
      description: "GitHub username or organization name",
    },
    type: {
      type: z.enum(["all", "owner", "member"]).default("owner"),
      description: "Type of repos to list",
    },
  };

  async execute(input: z.infer<typeof inputSchema>): Promise<string> {
    try {
      const response = await fetch(
        `https://api.github.com/users/${input.owner}/repos?type=${input.type}&sort=updated&per_page=30`,
        {
          headers: {
            Authorization: `Bearer ${GITHUB_TOKEN}`,
            Accept: "application/vnd.github.v3+json",
          },
        }
      );

      if (!response.ok) throw new Error(`GitHub API: ${response.status}`);
      const repos = await response.json();

      const summary = repos.map((r: Record<string, unknown>) => ({
        name: r.full_name,
        description: r.description,
        stars: r.stargazers_count,
        language: r.language,
        updatedAt: r.updated_at,
      }));

      return JSON.stringify(summary, null, 2);
    } catch (error) {
      const msg = error instanceof Error ? error.message : "Unknown error";
      return JSON.stringify({ error: msg });
    }
  }
}

const inputSchema = z.object({
  owner: z.string(),
  type: z.enum(["all", "owner", "member"]).default("owner"),
});
export default ListReposTool;

Step 3: Create the Issues Tool

Create src/tools/ListIssuesTool.ts:

import { MCPTool } from "mcp-framework";
import { z } from "zod";

const GITHUB_TOKEN = process.env.GITHUB_TOKEN || "";

class ListIssuesTool extends MCPTool<typeof inputSchema> {
  name = "list_issues";
  description = "List open issues for a GitHub repository";

  schema = {
    owner: {
      type: z.string(),
      description: "Repository owner",
    },
    repo: {
      type: z.string(),
      description: "Repository name",
    },
    state: {
      type: z.enum(["open", "closed", "all"]).default("open"),
      description: "Issue state filter",
    },
  };

  async execute(input: z.infer<typeof inputSchema>): Promise<string> {
    try {
      const response = await fetch(
        `https://api.github.com/repos/${input.owner}/${input.repo}/issues?state=${input.state}&per_page=20`,
        {
          headers: {
            Authorization: `Bearer ${GITHUB_TOKEN}`,
            Accept: "application/vnd.github.v3+json",
          },
        }
      );

      if (!response.ok) throw new Error(`GitHub API: ${response.status}`);
      const issues = await response.json();

      const summary = issues.map((i: Record<string, unknown>) => ({
        number: i.number,
        title: i.title,
        state: i.state,
        labels: (i.labels as Array<Record<string, unknown>>).map(
          (l) => l.name
        ),
        createdAt: i.created_at,
        author: (i.user as Record<string, unknown>)?.login,
      }));

      return JSON.stringify(summary, null, 2);
    } catch (error) {
      const msg = error instanceof Error ? error.message : "Unknown error";
      return JSON.stringify({ error: msg });
    }
  }
}

const inputSchema = z.object({
  owner: z.string(),
  repo: z.string(),
  state: z.enum(["open", "closed", "all"]).default("open"),
});
export default ListIssuesTool;

Step 4: Create the Create Issue Tool

Create src/tools/CreateIssueTool.ts:

import { MCPTool } from "mcp-framework";
import { z } from "zod";

const GITHUB_TOKEN = process.env.GITHUB_TOKEN || "";

class CreateIssueTool extends MCPTool<typeof inputSchema> {
  name = "create_issue";
  description = "Create a new issue in a GitHub repository";

  schema = {
    owner: {
      type: z.string(),
      description: "Repository owner",
    },
    repo: {
      type: z.string(),
      description: "Repository name",
    },
    title: {
      type: z.string(),
      description: "Issue title",
    },
    body: {
      type: z.string().optional(),
      description: "Issue body in Markdown",
    },
  };

  async execute(input: z.infer<typeof inputSchema>): Promise<string> {
    try {
      const response = await fetch(
        `https://api.github.com/repos/${input.owner}/${input.repo}/issues`,
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${GITHUB_TOKEN}`,
            Accept: "application/vnd.github.v3+json",
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            title: input.title,
            body: input.body,
          }),
        }
      );

      if (!response.ok) throw new Error(`GitHub API: ${response.status}`);
      const issue = await response.json();

      return JSON.stringify({
        number: issue.number,
        title: issue.title,
        url: issue.html_url,
        state: issue.state,
      }, null, 2);
    } catch (error) {
      const msg = error instanceof Error ? error.message : "Unknown error";
      return JSON.stringify({ error: msg });
    }
  }
}

const inputSchema = z.object({
  owner: z.string(),
  repo: z.string(),
  title: z.string(),
  body: z.string().optional(),
});
export default CreateIssueTool;

Step 5: Build and Connect

npm run build
{
  "mcpServers": {
    "github": {
      "command": "node",
      "args": ["/absolute/path/to/github-server/dist/index.js"],
      "env": {
        "GITHUB_TOKEN": "ghp_your_token_here"
      }
    }
  }
}

Chef's Notes

  • Use a fine-grained personal access token with only the permissions you need.
  • Never hardcode tokens — always use environment variables passed via the MCP config.
  • Rate limits apply: GitHub allows 5,000 requests/hour with authentication.
  • Extend with tools for PR creation, branch management, and code search.
  • mcp-framework auto-discovers all tools in src/tools/ — just add new files and rebuild.

More recipes: File Manager Server | Web Scraper | All Recipes

Built with mcp-framework by @QuantGeekDev — 3.3M+ downloads, validated by Anthropic.

Built with mcp-framework (3.3M+ downloads) — created by @QuantGeekDev and validated by Anthropic.