# Directory Structure

```
├── .dockerignore
├── .env.example
├── .github
│   └── workflows
│       └── ci.yml
├── .gitignore
├── Dockerfile
├── eslint.config.js
├── package.json
├── pnpm-lock.yaml
├── prettier.config.mjs
├── README.md
├── rolldown.config.ts
├── src
│   ├── app.ts
│   ├── common
│   │   └── github-schema.ts
│   ├── constants.ts
│   ├── env.ts
│   ├── tools
│   │   ├── get-issue.ts
│   │   ├── get-pull-request.ts
│   │   ├── list-repositories-issues.ts
│   │   ├── list-repositories-pull-requests.ts
│   │   ├── search-code.ts
│   │   ├── search-commits.ts
│   │   ├── search-issues.ts
│   │   ├── search-labels.ts
│   │   ├── search-repositories.ts
│   │   ├── search-topics.ts
│   │   └── search-users.ts
│   └── utils
│       └── gh-fetch.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
node_modules/
.env
.env.production

dist/

```

--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------

```
node_modules
.git
.gitignore
*.md
dist

.env
.env.production
.env.example
```

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
NODE_ENV=development
GITHUB_PERSONAL_ACCESS_TOKEN=your_personal_github_access_token


```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Github MCP Server

A [Model Context Protocol](https://github.com/modelcontextprotocol) Server for Github.

Provides integration with Github through MCP, allowing LLMs to interact with it.

[Github REST Api Docs](https://docs.github.com/en/rest)

## Installation

### Manual Installation

1. Create or get access token for your Github Account: [Guide](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token)

2. Add server config to Claude Desktop:

   - MacOS: ~/Library/Application Support/Claude/claude_desktop_config.json
   - Windows: [Check this Guide](https://gist.github.com/feveromo/7a340d7795fca1ccd535a5802b976e1f)

```json
{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "github-mcp-server"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "your_personal_github_access_token"
      }
    }
  }
}
```

## Components

### Tools

1.  `search_repositories`: Search GitHub for a repository.

    - Required inputs:
      - `query` (string): The query to search for repository.
      - `page` (number, default: 30, max: 100): Page number for pagination.
      - `per_page` (number, default: 30, max: 100): Number of results per page.

2.  `search_issues`: Search issues from a repository.

    - Required inputs:
      - `query` (string): The query to search for repository.
      - `page` (number, default: 1): Page number for pagination.
      - `per_page` (number, default: 30, max: 100): Number of results per page.
      - `order` (optional string, default: `desc`): Sort of order (`asc` or `desc`).
      - `sort` (optional string, default: `best match`): Sort field (can be one of: `comments`, `reactions`, `reactions-+1`, `reactions--1`, `reactions-smile`, `reactions-thinking_face`, `reactions-heart`, `reactions-tada`, `interactions`, `created` or `updated`).

3.  `search_commits`: Search commits from a repository.

    - Required inputs:
      - `query` (string): The query to search for repository.
      - `page` (number, default: 1): Page number for pagination.
      - `per_page` (number, default: 30, max: 100): Number of results per page.
      - `order` (optional string, default: `desc`): Sort of order (`asc` or `desc`).
      - `sort` (optional string, default: `best match`): Sort field (can be one of: `committer-date` or `author-date`).

4.  `search_code`: Search code from a repository.

    - Required inputs:
      - `query` (string): The query to search for repository.
      - `page` (number, default: 1): Page number for pagination.
      - `per_page` (number, default: 30, max: 100): Number of results per page.

5.  `search_users`: Search users from a repository.

    - Required inputs:
      - `query` (string): The query to search for repository.
      - `page` (number, default: 1): Page number for pagination.
      - `per_page` (number, default: 30, max: 100): Number of results per page.
      - `order` (optional string, default: `desc`): Sort of order (`asc` or `desc`).
      - `sort` (optional string, default: `best match`): Sort field (can be one of: `followers`, `repositories` or `joined`).

6.  `search_topics`: Search topics.

    - Required inputs:
      - `query` (string): The query to search for repository.
      - `page` (number, default: 1): Page number for pagination.
      - `per_page` (number, default: 30, max: 100): Number of results per page.

7.  `search_labels`: Search labels in a repository.

    - Required inputs:
      - `query` (string): The query to search for repository.
      - `page` (number, default: 1): Page number for pagination.
      - `per_page` (number, default: 30, max: 100): Number of results per page.
      - `order` (optional string, default: `desc`): Sort of order (`asc` or `desc`).
      - `sort` (optional string, default: `best match`): Sort field (can be one of: `created` or `updated`).

8.  `list_repositories_issues`: List issues from a repository.

    - Required inputs:
      - `owner` (string): The owner of the repository.
      - `repo` (string): The repository name.
      - `page` (optional number, default: 1): Page number for pagination.
      - `per_page` (optional number, default: 30, max: 100): Number of results per page.
      - `direction` (optional string, default: `desc`): Direction of sort (`asc` or `desc`).
      - `sort` (optional string, default: `created`): Sort field (can be one of: `created`, `comments` or `updated`).
      - `since` (optional string): Results last updated after the given time (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ.).
      - `labels` (optional string): Comma separated label names. Example: bug,ui,@high.
      - `milestone` (optional string): Milestone number.
      - `assignee` (optional string): Name of assignee user (`*` for all).
      - `creator` (optional string): The user that created the issue. (`*` for all).
      - `mentioned` (optional string): A user that's mentioned in the issue.

9.  `get_issue`: Get an issue from a repository.

    - Required inputs:
      - `owner` (string): The owner of the repository.
      - `repo` (string): The repository name.
      - `issue_number` (number): The issue number.

10. `list_repositories_pull_requests`: List pull requests from a repository.

    - Required inputs:
      - `owner` (string): The owner of the repository.
      - `repo` (string): The repository name.
      - `page` (optional number, default: 1): Page number for pagination.
      - `per_page` (optional number, default: 30, max: 100): Number of results per page.
      - `direction` (optional string, default: `desc`): Direction of sort (`asc` or `desc`).
      - `sort` (optional string, default: `created`): Sort field (can be one of: `created`, `popularity`, `long-running` or `updated`).
      - `head` (optional string): Filter pulls by head user or head organization and branch name in the format of user:ref-name or organization:ref-name (For example: github:new-script-format or octocat:test-branch).
      - `base` (optional string): Filter pulls by base branch name. (For example: gh-pages).

11. `get_pull_request`: Get a pull request from a repository.
    - Required inputs:
      - `owner` (string): The owner of the repository.
      - `repo` (string): The repository name.
      - `pull_request_number` (number): The pull request number.

## Usage examples

Some example prompts you can use to interact with Github:

1. "modelcontextprotocol" → execute the `search_repositories` tool to find repositories where modelcontextprotocol mentioned.
2. "What is the 739 issue on modelcontextprotocol servers repo" → execute the `get_issue` tool to find 739 issue from modelcontextprotocol servers repo.
3. "What is the 717 PR on modelcontextprotocol servers repo" → execute the `get_pull_request` tool to find 717 PR from modelcontextprotocol servers repo.

## Development

1. Install dependencies:

```shell
pnpm install
```

2. Configure Github Access token in `.env`:

```shell
GITHUB_PERSONAL_ACCESS_TOKEN=<your_personal_github_access_token>
```

3. Run locally with watch:

```shell
pnpm dev
```

4. Build the server:

```shell
pnpm build
```

5. Local debugging with inspector:

```shell
pnpm inspector
```

```

--------------------------------------------------------------------------------
/prettier.config.mjs:
--------------------------------------------------------------------------------

```
/**
 * @type {import('prettier').Config}
 */
const config = {
  arrowParens: "always",
  printWidth: 80,
  singleQuote: false,
  jsxSingleQuote: false,
  semi: true,
  trailingComma: "all",
  tabWidth: 2,
};

export default config;

```

--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------

```typescript
import { getUserAgent } from "universal-user-agent";

export const VERSION = "0.0.1";
export const GITHUB_API_BASE_URL = "https://api.github.com";
export const USER_AGENT = `modelcontextprotocol/servers/github/v${VERSION} ${getUserAgent()}`;

```

--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------

```javascript
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import neverThrowPlugin from "eslint-plugin-neverthrow";

export default tseslint.config(
  { ignores: ["dist"] },
  {
    extends: [js.configs.recommended, ...tseslint.configs.recommended],
    plugins: {
      neverthrow: neverThrowPlugin,
    },
    languageOptions: {
      ecmaVersion: 2022,
    },
  },
);

```

--------------------------------------------------------------------------------
/rolldown.config.ts:
--------------------------------------------------------------------------------

```typescript
import { defineConfig } from "rolldown";
import { minify } from "rollup-plugin-swc3";

export default defineConfig({
  input: "src/app.ts",
  platform: "node",
  treeshake: true,
  output: {
    file: "dist/app.js",
  },
  define: {
    "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
    "process.env.GITHUB_PERSONAL_ACCESS_TOKEN": JSON.stringify(
      process.env.GITHUB_PERSONAL_ACCESS_TOKEN,
    ),
  },
  plugins: [
    minify({
      module: true,
      mangle: {},
      compress: {},
    }),
  ],
});

```

--------------------------------------------------------------------------------
/src/env.ts:
--------------------------------------------------------------------------------

```typescript
import { createEnv } from "@t3-oss/env-core";
import { z } from "zod";

export const env = createEnv({
  emptyStringAsUndefined: true,

  clientPrefix: "PUBLIC_",

  client: {},

  shared: {
    NODE_ENV: z.enum(["development", "production"]),
  },

  server: {
    GITHUB_PERSONAL_ACCESS_TOKEN: z
      .string({
        required_error: "GITHUB_PERSONAL_ACCESS_TOKEN is required",
      })
      .min(1, "GITHUB_PERSONAL_ACCESS_TOKEN is required"),
  },

  runtimeEnv: {
    NODE_ENV: process.env.NODE_ENV,
    GITHUB_PERSONAL_ACCESS_TOKEN: process.env.GITHUB_PERSONAL_ACCESS_TOKEN,
  },
});

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "skipLibCheck": true,
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "noEmit": true,

    // Some stricter flags (disabled by default)
    "noFallthroughCasesInSwitch": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noPropertyAccessFromIndexSignature": false
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "dist"]
}

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
FROM node:22.12-bullseye-slim AS base

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

WORKDIR /app

FROM base AS prod-deps
COPY package.json pnpm-lock.yaml ./
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile

FROM base AS build
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile

COPY . .

ENV NODE_ENV="production"
ARG GITHUB_PERSONAL_ACCESS_TOKEN
ENV GITHUB_PERSONAL_ACCESS_TOKEN=$GITHUB_PERSONAL_ACCESS_TOKEN

RUN pnpm run build

FROM base
COPY --from=prod-deps /app/node_modules /node_modules
COPY --from=build /app/dist /dist

ENTRYPOINT  [ "node", "dist/app.js" ]

```

--------------------------------------------------------------------------------
/src/utils/gh-fetch.ts:
--------------------------------------------------------------------------------

```typescript
import { USER_AGENT } from "../constants.js";
import { env } from "../env.js";
import { err, ok } from "neverthrow";

export async function $github(url: string, options?: RequestInit) {
  const defaultHeaders = {
    Authorization: `Bearer ${env.GITHUB_PERSONAL_ACCESS_TOKEN}`,
    Accept: "application/vnd.github.v3+json",
    "Content-Type": "application/json",
    "User-Agent": USER_AGENT,
  };

  const headers = Object.assign(defaultHeaders, options?.headers ?? {});

  try {
    const response = await fetch(url, {
      headers,
      ...options,
    });

    if (!response.ok) {
      return err(new Error(`Failed to fetch ${url}: ${response.statusText}`));
    }

    return ok(response);
  } catch (error) {
    return err(new Error(`Failed to fetch ${url}: ${error}`));
  }
}

export async function $githubJson(url: string, options?: RequestInit) {
  try {
    const response = await $github(url, options);

    if (response.isErr()) return err(response.error);

    const json = await response.value.json();

    return ok(json);
  } catch (error) {
    return err(new Error(`Failed to fetch ${url}: ${error}`));
  }
}

```

--------------------------------------------------------------------------------
/src/tools/get-issue.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { GITHUB_API_BASE_URL } from "../constants.js";
import { $githubJson } from "../utils/gh-fetch.ts";
import { err, ok } from "neverthrow";
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import { zodToJsonSchema } from "zod-to-json-schema";

// @see https://docs.github.com/en/rest/issues/issues#get-an-issue get issue API docs

export const getIssueInputSchema = z.object({
  owner: z.string().describe("The owner of the repository"),
  repo: z.string().describe("The repository name"),
  issue_number: z.number().describe("The issue number"),
});

export const GET_ISSUE_TOOL: Tool = {
  name: "get_issue",
  description: "Get an issue from a repository",
  inputSchema: zodToJsonSchema(getIssueInputSchema) as Tool["inputSchema"],
};

export type GetIssueInput = z.output<typeof getIssueInputSchema>;

export async function getIssue(input: GetIssueInput) {
  const url = new URL(
    `/repos/${input.owner}/${input.repo}/issues/${input.issue_number}`,
    GITHUB_API_BASE_URL,
  );

  const json = await $githubJson(url.toString());

  if (json.isErr()) return err(json.error);

  try {
    // const data = gitHubSearchResponseSchema.parse(json.value);
    return ok(json.value);
  } catch (error) {
    return err(
      new Error(`Failed to parse github get issue response: ${error}`),
    );
  }
}

```

--------------------------------------------------------------------------------
/src/tools/get-pull-request.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { GITHUB_API_BASE_URL } from "../constants.js";
import { $githubJson } from "../utils/gh-fetch.ts";
import { err, ok } from "neverthrow";
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import { zodToJsonSchema } from "zod-to-json-schema";

// @see https://docs.github.com/en/rest/pulls/pulls#get-a-pull-request get pull request API docs

export const getPullRequestInputSchema = z.object({
  owner: z.string().describe("The owner of the repository"),
  repo: z.string().describe("The repository name"),
  pull_request_number: z.number().describe("The pull request number"),
});

export const GET_PULL_REQUEST_TOOL: Tool = {
  name: "get_pull_request",
  description: "Get a pull request from a repository",
  inputSchema: zodToJsonSchema(
    getPullRequestInputSchema,
  ) as Tool["inputSchema"],
};

export type GetPullRequestInput = z.output<typeof getPullRequestInputSchema>;

export async function getPullRequest(input: GetPullRequestInput) {
  const url = new URL(
    `/repos/${input.owner}/${input.repo}/pulls/${input.pull_request_number}`,
    GITHUB_API_BASE_URL,
  );

  const json = await $githubJson(url.toString());

  if (json.isErr()) return err(json.error);

  try {
    return ok(json.value);
  } catch (error) {
    return err(
      new Error(`Failed to parse github get pull request response: ${error}`),
    );
  }
}

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "github-mcp-server",
  "version": "1.0.0",
  "author": "Paras Solanki",
  "repository": {
    "type": "git",
    "url": "https://github.com/ParasSolanki/github-mcp-server.git"
  },
  "license": "MIT",
  "description": "A model context protocol server for GitHub API.",
  "type": "module",
  "keywords": [
    "github",
    "mcp",
    "server",
    "mcp",
    "api",
    "model context protocol"
  ],
  "packageManager": "pnpm@9.15.3",
  "bin": {
    "github-mcp-server": "dist/app.js"
  },
  "files": [
    "dist"
  ],
  "scripts": {
    "dev": "dotenvx run -- tsx watch src/app.ts",
    "build": "dotenvx run -f .env.production -- rolldown -c rolldown.config.ts",
    "inspector": "pnpm build && npx @modelcontextprotocol/inspector dist/app.js",
    "lint": "eslint .",
    "format": "prettier '**/*.{cjs,mjs,ts,tsx,md,json}' --ignore-path ./.gitignore --ignore-unknown --write",
    "format:check": "prettier '**/*.{cjs,mjs,ts,tsx,md,json}' --ignore-path ./.gitignore --ignore-unknown --check"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.6.1",
    "@t3-oss/env-core": "^0.12.0",
    "neverthrow": "^8.2.0",
    "universal-user-agent": "^7.0.2",
    "zod": "^3.24.2",
    "zod-to-json-schema": "^3.24.3"
  },
  "devDependencies": {
    "@dotenvx/dotenvx": "^1.38.3",
    "@eslint/js": "^9.21.0",
    "@types/node": "^22.13.8",
    "eslint-plugin-neverthrow": "^1.1.4",
    "prettier": "^3.5.3",
    "rolldown": "1.0.0-beta.3",
    "rollup-plugin-swc3": "^0.12.1",
    "tsx": "^4.19.3",
    "typescript": "^5.7.2",
    "typescript-eslint": "^8.25.0"
  }
}

```

--------------------------------------------------------------------------------
/src/tools/search-code.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { GITHUB_API_BASE_URL } from "../constants.js";
import { $githubJson } from "../utils/gh-fetch.ts";
import { err, ok } from "neverthrow";
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import { zodToJsonSchema } from "zod-to-json-schema";

// @see https://docs.github.com/en/rest/search/search#search-code search code API docs

export const searchCodeInputSchema = z.object({
  query: z
    .string()
    .describe(
      "The query to search for (see Github search query syntax). The query contains one or more search keywords and qualifiers. Qualifiers allow you to limit your search to specific areas of GitHub. The REST API supports the same qualifiers as the web interface for GitHub. ",
    ),
  page: z.number().describe("Page number for pagination (default: 1)"),
  per_page: z
    .number()
    .describe("Number of results per page (default: 30, max: 100)"),
});

export const SEARCH_CODE_TOOL: Tool = {
  name: "search_code",
  description: "Search code from a repository",
  inputSchema: zodToJsonSchema(searchCodeInputSchema) as Tool["inputSchema"],
};

export type SearchCodeInput = z.output<typeof searchCodeInputSchema>;

export async function searchCode(input: SearchCodeInput) {
  const url = new URL(`/search/code`, GITHUB_API_BASE_URL);

  url.searchParams.set("q", input.query);

  if (input.page) url.searchParams.set("page", input.page.toString());

  if (input.per_page)
    url.searchParams.set("per_page", input.per_page.toString());

  const json = await $githubJson(url.toString());

  if (json.isErr()) return err(json.error);

  return ok(json.value);
}

```

--------------------------------------------------------------------------------
/src/tools/search-topics.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { GITHUB_API_BASE_URL } from "../constants.js";
import { $githubJson } from "../utils/gh-fetch.ts";
import { err, ok } from "neverthrow";
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import { zodToJsonSchema } from "zod-to-json-schema";

// @see https://docs.github.com/en/rest/search/search#search-topics search topics API docs

export const searchTopicsInputSchema = z.object({
  query: z
    .string()
    .describe(
      "The query to search for (see Github search query syntax). The query contains one or more search keywords and qualifiers. Qualifiers allow you to limit your search to specific areas of GitHub. The REST API supports the same qualifiers as the web interface for GitHub. ",
    ),
  page: z.number().describe("Page number for pagination (default: 1)"),
  per_page: z
    .number()
    .describe("Number of results per page (default: 30, max: 100)"),
});

export const SEARCH_TOPICS_TOOL: Tool = {
  name: "search_topics",
  description: "Search topics",
  inputSchema: zodToJsonSchema(searchTopicsInputSchema) as Tool["inputSchema"],
};

export type SearchTopicsInput = z.output<typeof searchTopicsInputSchema>;

export async function searchTopics(input: SearchTopicsInput) {
  const url = new URL(`/search/topics`, GITHUB_API_BASE_URL);

  url.searchParams.set("q", input.query);

  if (input.page) url.searchParams.set("page", input.page.toString());

  if (input.per_page)
    url.searchParams.set("per_page", input.per_page.toString());

  const json = await $githubJson(url.toString());

  if (json.isErr()) return err(json.error);

  return ok(json.value);
}

```

--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------

```yaml
name: CI

on:
  pull_request:
    branches: ["*"]
  merge_group:

jobs:
  prettier:
    runs-on: ubuntu-latest
    name: Run prettier
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Install Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22

      - uses: pnpm/action-setup@v3
        name: Install pnpm
        id: pnpm-install
        with:
          run_install: false

      - name: Get pnpm store directory
        id: pnpm-cache
        run: |
          echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT

      - uses: actions/cache@v4
        name: Setup pnpm cache
        with:
          path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Install dependencies
        run: pnpm install

      - run: pnpm format:check

  lint:
    runs-on: ubuntu-latest
    name: Run ESLint
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Install Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22

      - uses: pnpm/action-setup@v3
        name: Install pnpm
        id: pnpm-install
        with:
          run_install: false

      - name: Get pnpm store directory
        id: pnpm-cache
        run: |
          echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT

      - uses: actions/cache@v4
        name: Setup pnpm cache
        with:
          path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Install dependencies
        run: pnpm install

      - run: pnpm lint

```

--------------------------------------------------------------------------------
/src/tools/search-labels.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { GITHUB_API_BASE_URL } from "../constants.js";
import { $githubJson } from "../utils/gh-fetch.ts";
import { err, ok } from "neverthrow";
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import { zodToJsonSchema } from "zod-to-json-schema";

// @see https://docs.github.com/en/rest/search/search#search-labels search labels API docs

export const searchLabelsInputSchema = z.object({
  query: z
    .string()
    .describe(
      "The search keywords. This endpoint does not accept qualifiers in the query. (see Github search query syntax)",
    ),
  page: z.number().describe("Page number for pagination (default: 1)"),
  per_page: z
    .number()
    .describe("Number of results per page (default: 30, max: 100)"),
  sort: z
    .enum(["created", "updated"])
    .optional()
    .describe(
      "Sorts the results of your query by when the label was created or updated. (default: best match)",
    ),
  order: z
    .enum(["asc", "desc"])
    .optional()
    .describe(
      "Determines whether the first search result returned is the highest number of matches (desc) or lowest number of matches (asc). This parameter is ignored unless you provide sort. (default: desc)",
    ),
});

export const SEARCH_LABELS_TOOL: Tool = {
  name: "search_labels",
  description: "Search labels in a repository",
  inputSchema: zodToJsonSchema(searchLabelsInputSchema) as Tool["inputSchema"],
};

export type SearchLabelsInput = z.output<typeof searchLabelsInputSchema>;

export async function searchLabels(input: SearchLabelsInput) {
  const url = new URL(`/search/labels`, GITHUB_API_BASE_URL);

  url.searchParams.set("q", input.query);

  if (input.page) url.searchParams.set("page", input.page.toString());

  if (input.per_page)
    url.searchParams.set("per_page", input.per_page.toString());

  if (input.sort) url.searchParams.set("sort", input.sort);

  if (input.order) url.searchParams.set("order", input.order);

  const json = await $githubJson(url.toString());

  if (json.isErr()) return err(json.error);

  return ok(json.value);
}

```

--------------------------------------------------------------------------------
/src/tools/search-commits.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { GITHUB_API_BASE_URL } from "../constants.js";
import { $githubJson } from "../utils/gh-fetch.ts";
import { err, ok } from "neverthrow";
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import { zodToJsonSchema } from "zod-to-json-schema";

// @see https://docs.github.com/en/rest/search/search#search-commits search commits API docs

export const searchCommitsInputSchema = z.object({
  query: z
    .string()
    .describe(
      "The query to search for (see Github search query syntax). The query contains one or more search keywords and qualifiers. Qualifiers allow you to limit your search to specific areas of GitHub. The REST API supports the same qualifiers as the web interface for GitHub. ",
    ),
  page: z.number().describe("Page number for pagination (default: 1)"),
  per_page: z
    .number()
    .describe("Number of results per page (default: 30, max: 100)"),
  sort: z
    .enum(["committer-date", "author-date"])
    .optional()
    .describe(
      "Sorts the results of your query by author-date or committer-date. (default: best match)",
    ),
  order: z
    .enum(["asc", "desc"])
    .optional()
    .describe(
      "Determines whether the first search result returned is the highest number of matches (desc) or lowest number of matches (asc). This parameter is ignored unless you provide sort. (default: desc)",
    ),
});

export const SEARCH_COMMITS_TOOL: Tool = {
  name: "search_commits",
  description: "Search commits from a repository",
  inputSchema: zodToJsonSchema(searchCommitsInputSchema) as Tool["inputSchema"],
};

export type SearchCommitsInput = z.output<typeof searchCommitsInputSchema>;

export async function searchCommits(input: SearchCommitsInput) {
  const url = new URL(`/search/commits`, GITHUB_API_BASE_URL);

  url.searchParams.set("q", input.query);

  if (input.page) url.searchParams.set("page", input.page.toString());

  if (input.per_page)
    url.searchParams.set("per_page", input.per_page.toString());

  if (input.sort) url.searchParams.set("sort", input.sort);

  if (input.order) url.searchParams.set("order", input.order);

  const json = await $githubJson(url.toString());

  if (json.isErr()) return err(json.error);

  return ok(json.value);
}

```

--------------------------------------------------------------------------------
/src/tools/search-users.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { GITHUB_API_BASE_URL } from "../constants.js";
import { $githubJson } from "../utils/gh-fetch.ts";
import { err, ok } from "neverthrow";
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import { zodToJsonSchema } from "zod-to-json-schema";

// @see https://docs.github.com/en/rest/search/search#search-users search users API docs

export const searchUsersInputSchema = z.object({
  query: z
    .string()
    .describe(
      "The query to search for (see Github search query syntax). The query contains one or more search keywords and qualifiers. Qualifiers allow you to limit your search to specific areas of GitHub. The REST API supports the same qualifiers as the web interface for GitHub. ",
    ),
  page: z.number().describe("Page number for pagination (default: 1)"),
  per_page: z
    .number()
    .describe("Number of results per page (default: 30, max: 100)"),
  sort: z
    .enum(["followers", "repositories", "joined"])
    .optional()
    .describe(
      "Sorts the results of your query by number of followers or repositories, or when the person joined GitHub. Default: best match",
    ),
  order: z
    .enum(["asc", "desc"])
    .optional()
    .describe(
      "Determines whether the first search result returned is the highest number of matches (desc) or lowest number of matches (asc). This parameter is ignored unless you provide sort. (default: desc).",
    ),
});

export const SEARCH_USERS_TOOL: Tool = {
  name: "search_users",
  description: "Search users from a repository",
  inputSchema: zodToJsonSchema(searchUsersInputSchema) as Tool["inputSchema"],
};

export type SearchUsersInput = z.output<typeof searchUsersInputSchema>;

export async function searchUsers(input: SearchUsersInput) {
  const url = new URL(`/search/users`, GITHUB_API_BASE_URL);

  url.searchParams.set("q", input.query);

  if (input.page) url.searchParams.set("page", input.page.toString());

  if (input.per_page)
    url.searchParams.set("per_page", input.per_page.toString());

  if (input.sort) url.searchParams.set("sort", input.sort);

  if (input.order) url.searchParams.set("order", input.order);

  const json = await $githubJson(url.toString());

  if (json.isErr()) return err(json.error);

  return ok(json.value);
}

```

--------------------------------------------------------------------------------
/src/common/github-schema.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";

export const gitHubAuthorSchema = z.object({
  name: z.string().describe("The name of the author"),
  email: z.string().describe("The email of the author"),
  date: z.string().describe("The date of the commit"),
});

export const gitHubOwnerSchema = z.object({
  login: z.string().describe("The login name of the owner"),
  id: z.number().describe("The ID of the owner"),
  node_id: z.string().describe("The node ID of the owner"),
  avatar_url: z.string().describe("The avatar URL of the owner"),
  url: z.string().describe("The URL of the owner"),
  html_url: z.string().describe("The HTML URL of the owner"),
  type: z.string().describe("The type of the owner"),
});

export const gitHubRepositorySchema = z.object({
  id: z.number().describe("The ID of the repository"),
  node_id: z.string().describe("The node ID of the repository"),
  name: z.string().describe("The name of the repository"),
  full_name: z.string().describe("The full name of the repository"),
  private: z.boolean().describe("Whether the repository is private"),
  owner: gitHubOwnerSchema,
  html_url: z.string().describe("The HTML URL of the repository"),
  description: z
    .string()
    .nullable()
    .describe("The description of the repository"),
  fork: z.boolean().describe("Whether the repository is a fork"),
  url: z.string().describe("The URL of the repository"),
  created_at: z.string().describe("The date the repository was created"),
  updated_at: z.string().describe("The date the repository was updated"),
  pushed_at: z.string().describe("The date the repository was pushed"),
  git_url: z.string().describe("The Git URL of the repository"),
  ssh_url: z.string().describe("The SSH URL of the repository"),
  clone_url: z.string().describe("The clone URL of the repository"),
  default_branch: z.string().describe("The default branch of the repository"),
});

export const gitHubSearchResponseSchema = z.object({
  total_count: z.number().describe("The total number of results"),
  incomplete_results: z
    .boolean()
    .describe("Whether the results are incomplete"),
  items: z.array(gitHubRepositorySchema).describe("The repositories"),
});

export type GitHubAuthor = z.infer<typeof gitHubAuthorSchema>;
export type GitHubRepository = z.infer<typeof gitHubRepositorySchema>;
export type GitHubSearchResponse = z.infer<typeof gitHubSearchResponseSchema>;

```

--------------------------------------------------------------------------------
/src/tools/search-repositories.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { gitHubSearchResponseSchema } from "../common/github-schema.js";
import { GITHUB_API_BASE_URL } from "../constants.js";
import { $githubJson } from "../utils/gh-fetch.ts";
import { err, ok } from "neverthrow";
import { zodToJsonSchema } from "zod-to-json-schema";
import type { Tool } from "@modelcontextprotocol/sdk/types.js";

// @see https://docs.github.com/en/rest/search/search#search-repositories search repositories API docs

export const searchRepositoriesInputSchema = z.object({
  query: z
    .string()
    .describe(
      "The query contains one or more search keywords and qualifiers. Qualifiers allow you to limit your search to specific areas of GitHub. The REST API supports the same qualifiers as the web interface for GitHub. (see Github search query syntax)",
    ),
  page: z.number().describe("Page number for pagination (default: 1)"),
  per_page: z
    .number()
    .describe("Number of results per page (default: 30, max: 100)"),
  sort: z
    .enum(["stars", "forks", "help-wanted-issues", "updated"])
    .optional()
    .describe(
      "Sorts the results of your query by the number of stars, forks, or help-wanted-issues or how recently the items were updated. Default: best match",
    ),
  order: z
    .enum(["asc", "desc"])
    .optional()
    .describe(
      "Determines whether the first search result returned is the highest number of matches (desc) or lowest number of matches (asc). This parameter is ignored unless you provide sort. (default: desc)",
    ),
});

export const SEARCH_REPOSITORIES_TOOL: Tool = {
  name: "search_repositories",
  description: "Search GitHub for a repository",
  inputSchema: zodToJsonSchema(
    searchRepositoriesInputSchema,
  ) as Tool["inputSchema"],
};

export type SearchRepositoriesInput = z.output<
  typeof searchRepositoriesInputSchema
>;

export async function searchRepositories(input: SearchRepositoriesInput) {
  const url = new URL("/search/repositories", GITHUB_API_BASE_URL);

  url.searchParams.set("q", input.query);
  url.searchParams.set("page", input.page.toString());
  url.searchParams.set("per_page", input.per_page.toString());

  const json = await $githubJson(url.toString());

  if (json.isErr()) return err(json.error);

  try {
    const data = gitHubSearchResponseSchema.parse(json.value);
    return ok(data);
  } catch (error) {
    return err(
      new Error(
        `Failed to parse github search repositories response: ${error}`,
      ),
    );
  }
}

```

--------------------------------------------------------------------------------
/src/tools/search-issues.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { GITHUB_API_BASE_URL } from "../constants.js";
import { $githubJson } from "../utils/gh-fetch.ts";
import { err, ok } from "neverthrow";
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import { zodToJsonSchema } from "zod-to-json-schema";

export const searchIssuesInputSchema = z.object({
  query: z
    .string()
    .describe(
      "The query to search for (see Github search query syntax). The query contains one or more search keywords and qualifiers. Qualifiers allow you to limit your search to specific areas of GitHub. The REST API supports the same qualifiers as the web interface for GitHub. ",
    ),
  page: z.number().describe("Page number for pagination (default: 1)"),
  per_page: z
    .number()
    .describe("Number of results per page (default: 30, max: 100)"),
  sort: z
    .enum([
      "comments",
      "reactions",
      "reactions-+1",
      "reactions--1",
      "reactions-smile",
      "reactions-thinking_face",
      "reactions-heart",
      "reactions-tada",
      "interactions",
      "created",
      "updated",
    ])
    .optional()
    .describe(
      "Sorts the results of your query by the number of comments, reactions, reactions-+1, reactions--1, reactions-smile, reactions-thinking_face, reactions-heart, reactions-tada, or interactions. You can also sort results by how recently the items were created or updated. Default: best match",
    ),
  order: z
    .enum(["asc", "desc"])
    .optional()
    .describe(
      "Determines whether the first search result returned is the highest number of matches (desc) or lowest number of matches (asc). This parameter is ignored unless you provide sort. (default: desc)",
    ),
});

export const SEARCH_ISSUES_TOOL: Tool = {
  name: "search_issues",
  description: "Search issues from a repository",
  inputSchema: zodToJsonSchema(searchIssuesInputSchema) as Tool["inputSchema"],
};

export type SearchIssuesInput = z.output<typeof searchIssuesInputSchema>;

export async function searchIssues(input: SearchIssuesInput) {
  const url = new URL(`/search/issues`, GITHUB_API_BASE_URL);

  url.searchParams.set("q", input.query);

  if (input.page) url.searchParams.set("page", input.page.toString());

  if (input.per_page)
    url.searchParams.set("per_page", input.per_page.toString());

  if (input.sort) url.searchParams.set("sort", input.sort);

  if (input.order) url.searchParams.set("order", input.order);

  const json = await $githubJson(url.toString());

  if (json.isErr()) return err(json.error);

  return ok(json.value);
}

```

--------------------------------------------------------------------------------
/src/tools/list-repositories-pull-requests.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { GITHUB_API_BASE_URL } from "../constants.ts";
import { $githubJson } from "../utils/gh-fetch.ts";
import { err, ok } from "neverthrow";
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import { zodToJsonSchema } from "zod-to-json-schema";

// @see https://docs.github.com/en/rest/pulls/pulls#list-pull-requests list pull requests API docs

export const listRepositoriesPullRequestsInputSchema = z.object({
  owner: z.string().describe("The owner of the repository"),
  repo: z.string().describe("The repository name"),
  state: z
    .enum(["open", "closed", "all"])
    .describe("The state of the pull requests to list (default: open)"),
  page: z
    .number()
    .optional()
    .describe("The page number for pagination (default: 1)"),
  per_page: z
    .number()
    .optional()
    .describe("The number of results per page (default: 30, max: 100)."),
  sort: z
    .enum(["created", "updated", "popularity", "long-running"])
    .optional()
    .describe(
      "What to sort results by. popularity will sort by the number of comments. long-running will sort by date created and will limit the results to pull requests that have been open for more than a month and have had activity within the past month. (default: created)",
    ),
  direction: z
    .enum(["asc", "desc"])
    .optional()
    .describe(
      "The direction of the sort. Default: desc when sort is created or sort is not specified, otherwise asc",
    ),
  head: z
    .string()
    .optional()
    .describe(
      "Filter pulls by head user or head organization and branch name in the format of user:ref-name or organization:ref-name. For example: github:new-script-format or octocat:test-branch",
    ),
  base: z
    .string()
    .optional()
    .describe("Filter pulls by base branch name. Example: gh-pages."),
});

export const LIST_REPOSITORIES_PULL_REQUESTS_TOOL: Tool = {
  name: "list_repositories_pull_requests",
  description: "List pull requests from a repository",
  inputSchema: zodToJsonSchema(
    listRepositoriesPullRequestsInputSchema,
  ) as Tool["inputSchema"],
};

export type ListRepositoriesPullRequestsInput = z.output<
  typeof listRepositoriesPullRequestsInputSchema
>;

export async function listRepositoriesPullRequests(
  input: ListRepositoriesPullRequestsInput,
) {
  const url = new URL(
    `/repos/${input.owner}/${input.repo}/pulls`,
    GITHUB_API_BASE_URL,
  );

  if (input.state) url.searchParams.set("state", input.state);

  if (input.page) url.searchParams.set("page", input.page.toString());

  if (input.per_page)
    url.searchParams.set("per_page", input.per_page.toString());

  if (input.sort) url.searchParams.set("sort", input.sort);

  if (input.direction) url.searchParams.set("direction", input.direction);

  if (input.head) url.searchParams.set("head", input.head);

  if (input.base) url.searchParams.set("base", input.base);

  const json = await $githubJson(url.toString());

  if (json.isErr()) return err(json.error);

  return ok(json.value);
}

```

--------------------------------------------------------------------------------
/src/tools/list-repositories-issues.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { GITHUB_API_BASE_URL } from "../constants.ts";
import { $githubJson } from "../utils/gh-fetch.ts";
import { err, ok } from "neverthrow";
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import { zodToJsonSchema } from "zod-to-json-schema";

// @see https://docs.github.com/en/rest/issues/issues#list-repository-issues list repository issues API docs

export const listRepositoriesIssuesInputSchema = z.object({
  owner: z.string().describe("The owner of the repository"),
  repo: z.string().describe("The repository name"),
  state: z
    .enum(["open", "closed", "all"])
    .describe("The state of the issues to list (default: open)"),
  page: z
    .number()
    .optional()
    .describe("The page number for pagination (default: 1)"),
  per_page: z
    .number()
    .optional()
    .describe("The number of results per page (default: 30, max: 100)."),
  sort: z
    .enum(["created", "updated", "comments"])
    .optional()
    .describe("The sort order of the issues, (default: created)"),
  direction: z
    .enum(["asc", "desc"])
    .optional()
    .describe("The direction of the sort order (default: desc)"),
  since: z
    .string()
    .optional()
    .describe(
      "Only show results that were last updated after the given time. This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ.",
    ),
  labels: z
    .string()
    .optional()
    .describe("A list of comma separated label names. Example: bug,ui,@high"),
  milestone: z
    .string()
    .optional()
    .describe(
      "If an integer is passed, it should refer to a milestone by its number field. If the string * is passed, issues with any milestone are accepted. If the string none is passed, issues without milestones are returned.",
    ),
  assignee: z
    .string()
    .optional()
    .describe(
      "Can be the name of a user. Pass in none for issues with no assigned user, and * for issues assigned to any user.",
    ),
  creator: z.string().optional().describe("The user that created the issue."),
  mentioned: z
    .string()
    .optional()
    .describe("A user that's mentioned in the issue."),
});

export const LIST_REPOSITORIES_ISSUES_TOOL: Tool = {
  name: "list_repositories_issues",
  description: "List issues from a repository",
  inputSchema: zodToJsonSchema(
    listRepositoriesIssuesInputSchema,
  ) as Tool["inputSchema"],
};

export type ListRepositoriesIssuesInput = z.output<
  typeof listRepositoriesIssuesInputSchema
>;

export async function listRepositoriesIssues(
  input: ListRepositoriesIssuesInput,
) {
  const url = new URL(
    `/repos/${input.owner}/${input.repo}/issues`,
    GITHUB_API_BASE_URL,
  );

  if (input.state) url.searchParams.set("state", input.state);

  if (input.page) url.searchParams.set("page", input.page.toString());

  if (input.per_page)
    url.searchParams.set("per_page", input.per_page.toString());

  if (input.sort) url.searchParams.set("sort", input.sort);

  if (input.direction) url.searchParams.set("direction", input.direction);

  if (input.since) url.searchParams.set("since", input.since);

  if (input.labels) url.searchParams.set("labels", input.labels);

  if (input.milestone) url.searchParams.set("milestone", input.milestone);

  if (input.assignee) url.searchParams.set("assignee", input.assignee);

  if (input.creator) url.searchParams.set("creator", input.creator);

  if (input.mentioned) url.searchParams.set("mentioned", input.mentioned);

  const json = await $githubJson(url.toString());

  if (json.isErr()) return err(json.error);

  return ok(json.value);
}

```

--------------------------------------------------------------------------------
/src/app.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import {
  SEARCH_REPOSITORIES_TOOL,
  searchRepositories,
  searchRepositoriesInputSchema,
} from "./tools/search-repositories.js";
import { VERSION } from "./constants.js";
import {
  GET_ISSUE_TOOL,
  getIssue,
  getIssueInputSchema,
} from "./tools/get-issue.ts";
import {
  GET_PULL_REQUEST_TOOL,
  getPullRequest,
  getPullRequestInputSchema,
} from "./tools/get-pull-request.ts";
import {
  LIST_REPOSITORIES_ISSUES_TOOL,
  listRepositoriesIssues,
  listRepositoriesIssuesInputSchema,
} from "./tools/list-repositories-issues.ts";
import {
  LIST_REPOSITORIES_PULL_REQUESTS_TOOL,
  listRepositoriesPullRequests,
  listRepositoriesPullRequestsInputSchema,
} from "./tools/list-repositories-pull-requests.ts";
import {
  SEARCH_ISSUES_TOOL,
  searchIssues,
  searchIssuesInputSchema,
} from "./tools/search-issues.ts";
import {
  SEARCH_CODE_TOOL,
  searchCode,
  searchCodeInputSchema,
} from "./tools/search-code.ts";
import {
  SEARCH_USERS_TOOL,
  searchUsers,
  searchUsersInputSchema,
} from "./tools/search-users.ts";
import {
  SEARCH_COMMITS_TOOL,
  searchCommits,
  searchCommitsInputSchema,
} from "./tools/search-commits.ts";
import {
  SEARCH_TOPICS_TOOL,
  searchTopics,
  searchTopicsInputSchema,
} from "./tools/search-topics.ts";
import {
  SEARCH_LABELS_TOOL,
  searchLabels,
  searchLabelsInputSchema,
} from "./tools/search-labels.ts";

const server = new Server(
  { name: "Github MCP Server", version: VERSION },
  { capabilities: { tools: {} } },
);

export const tools = [
  // search
  SEARCH_REPOSITORIES_TOOL,
  SEARCH_ISSUES_TOOL,
  SEARCH_CODE_TOOL,
  SEARCH_USERS_TOOL,
  SEARCH_COMMITS_TOOL,
  SEARCH_TOPICS_TOOL,
  SEARCH_LABELS_TOOL,

  // issues
  GET_ISSUE_TOOL,
  LIST_REPOSITORIES_ISSUES_TOOL,

  // pull requests
  LIST_REPOSITORIES_PULL_REQUESTS_TOOL,
  GET_PULL_REQUEST_TOOL,
] satisfies Tool[];

server.setRequestHandler(ListToolsRequestSchema, async () => {
  return { tools };
});

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  try {
    const name = request.params.name;
    const args = request.params.arguments;

    if (!args) throw new Error("No arguments provided");

    if (name === SEARCH_REPOSITORIES_TOOL.name) {
      const input = searchRepositoriesInputSchema.safeParse(args);

      if (!input.success) {
        return {
          isError: true,
          content: [{ type: "text", text: "Invalid input" }],
        };
      }

      const result = await searchRepositories(input.data);

      if (result.isErr()) {
        return {
          isError: true,
          content: [{ type: "text", text: "An error occurred" }],
        };
      }

      return {
        content: [
          { type: "text", text: JSON.stringify(result.value, null, 2) },
        ],
      };
    }

    if (name === GET_ISSUE_TOOL.name) {
      const input = getIssueInputSchema.safeParse(args);

      if (!input.success) {
        return {
          isError: true,
          content: [{ type: "text", text: "Invalid input" }],
        };
      }

      const result = await getIssue(input.data);

      if (result.isErr()) {
        return {
          isError: true,
          content: [{ type: "text", text: "An error occurred" }],
        };
      }

      return {
        content: [
          { type: "text", text: JSON.stringify(result.value, null, 2) },
        ],
      };
    }

    if (name === GET_PULL_REQUEST_TOOL.name) {
      const input = getPullRequestInputSchema.safeParse(args);

      if (!input.success) {
        return {
          isError: true,
          content: [{ type: "text", text: "Invalid input" }],
        };
      }

      const result = await getPullRequest(input.data);

      if (result.isErr()) {
        return {
          isError: true,
          content: [{ type: "text", text: "An error occurred" }],
        };
      }

      return {
        content: [
          { type: "text", text: JSON.stringify(result.value, null, 2) },
        ],
      };
    }

    if (name === LIST_REPOSITORIES_ISSUES_TOOL.name) {
      const input = listRepositoriesIssuesInputSchema.safeParse(args);

      if (!input.success) {
        return {
          isError: true,
          content: [{ type: "text", text: "Invalid input" }],
        };
      }

      const result = await listRepositoriesIssues(input.data);

      if (result.isErr()) {
        return {
          isError: true,
          content: [{ type: "text", text: "An error occurred" }],
        };
      }

      return {
        content: [
          { type: "text", text: JSON.stringify(result.value, null, 2) },
        ],
      };
    }

    if (name === LIST_REPOSITORIES_PULL_REQUESTS_TOOL.name) {
      const input = listRepositoriesPullRequestsInputSchema.safeParse(args);

      if (!input.success) {
        return {
          isError: true,
          content: [{ type: "text", text: "Invalid input" }],
        };
      }

      const result = await listRepositoriesPullRequests(input.data);

      if (result.isErr()) {
        return {
          isError: true,
          content: [{ type: "text", text: "An error occurred" }],
        };
      }

      return {
        content: [
          { type: "text", text: JSON.stringify(result.value, null, 2) },
        ],
      };
    }

    if (name === SEARCH_ISSUES_TOOL.name) {
      const input = searchIssuesInputSchema.safeParse(args);

      if (!input.success) {
        return {
          isError: true,
          content: [{ type: "text", text: "Invalid input" }],
        };
      }

      const result = await searchIssues(input.data);

      if (result.isErr()) {
        return {
          isError: true,
          content: [{ type: "text", text: "An error occurred" }],
        };
      }

      return {
        content: [
          { type: "text", text: JSON.stringify(result.value, null, 2) },
        ],
      };
    }

    if (name === SEARCH_CODE_TOOL.name) {
      const input = searchCodeInputSchema.safeParse(args);

      if (!input.success) {
        return {
          isError: true,
          content: [{ type: "text", text: "Invalid input" }],
        };
      }

      const result = await searchCode(input.data);

      if (result.isErr()) {
        return {
          isError: true,
          content: [{ type: "text", text: "An error occurred" }],
        };
      }

      return {
        content: [
          { type: "text", text: JSON.stringify(result.value, null, 2) },
        ],
      };
    }

    if (name === SEARCH_USERS_TOOL.name) {
      const input = searchUsersInputSchema.safeParse(args);

      if (!input.success) {
        return {
          isError: true,
          content: [{ type: "text", text: "Invalid input" }],
        };
      }

      const result = await searchUsers(input.data);

      if (result.isErr()) {
        return {
          isError: true,
          content: [{ type: "text", text: "An error occurred" }],
        };
      }

      return {
        content: [
          { type: "text", text: JSON.stringify(result.value, null, 2) },
        ],
      };
    }

    if (name === SEARCH_COMMITS_TOOL.name) {
      const input = searchCommitsInputSchema.safeParse(args);

      if (!input.success) {
        return {
          isError: true,
          content: [{ type: "text", text: "Invalid input" }],
        };
      }

      const result = await searchCommits(input.data);

      if (result.isErr()) {
        return {
          isError: true,
          content: [{ type: "text", text: "An error occurred" }],
        };
      }

      return {
        content: [
          { type: "text", text: JSON.stringify(result.value, null, 2) },
        ],
      };
    }

    if (name === SEARCH_TOPICS_TOOL.name) {
      const input = searchTopicsInputSchema.safeParse(args);

      if (!input.success) {
        return {
          isError: true,
          content: [{ type: "text", text: "Invalid input" }],
        };
      }

      const result = await searchTopics(input.data);

      if (result.isErr()) {
        return {
          isError: true,
          content: [{ type: "text", text: "An error occurred" }],
        };
      }

      return {
        content: [
          { type: "text", text: JSON.stringify(result.value, null, 2) },
        ],
      };
    }

    if (name === SEARCH_LABELS_TOOL.name) {
      const input = searchLabelsInputSchema.safeParse(args);

      if (!input.success) {
        return {
          isError: true,
          content: [{ type: "text", text: "Invalid input" }],
        };
      }

      const result = await searchLabels(input.data);

      if (result.isErr()) {
        return {
          isError: true,
          content: [{ type: "text", text: "An error occurred" }],
        };
      }

      return {
        content: [
          { type: "text", text: JSON.stringify(result.value, null, 2) },
        ],
      };
    }
    throw new Error(`Unknown tool: ${name}`);
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
  } catch (error) {
    return {
      isError: true,
      content: [{ type: "text", text: "An error occurred" }],
    };
  }
});

async function run() {
  const transport = new StdioServerTransport();

  await server.connect(transport);
}

run().catch((error) => {
  console.error("Fatal error in run()", error);
  process.exit(1);
});

```

