GitHubのプルリクエストを操作する最小限のMCPサーバーを実装してみた

## はじめに

近年、AIエージェントと外部ツールやデータソースを連携させるための標準プロトコルとして、**MCP（Model Context Protocol）** が注目されています。MCPを利用することで、AIエージェントが外部のAPIやデータにアクセスし、より高度なタスクを実行できるようになります。 ￼

本記事では、MCPの概要と、GitHubのプルリクエストを操作する最小限のMCPサーバー「mcp-gh-pr-mini」の実装してみたので、その実装方法を紹介します。

## MCPとは？

**MCP（Model Context Protocol）** はAIモデルが外部のツールやデータソースと標準化された方法で連携するためのオープンプロトコルです。これによりAIエージェントがさまざまな外部リソースとシームレスに統合され、高度なタスクを実行できるようになります。 ￼

MCPの詳細については以下の公式ドキュメントが参考になります。
* [Model Context Protocol: Introduction](https://modelcontextprotocol.io/introduction)

今回の記事ではNode.js(TypeScript)で書いてますが、上記リンク先にあるように`Python`, `Java`, `Kotlin`, `C#`でも作成できます。

## mcp-gh-pr-miniの概要

mcp-gh-pr-miniはGitHubのプルリクエストを操作するための最小限のMCPサーバーです。このサーバーを使用することで、AIエージェントが以下の操作を実行できるようになります。
* プルリクエストの作成
* プルリクエストの一覧取得
* プルリクエストの差分取得
* プルリクエストへのレビュアー追加
* プルリクエストへのコメント追加
* プルリクエストのコメント取得

このプロジェクトのソースコードは、以下のリポジトリで公開しています。

[mcp-gh-pr-mini GitHubリポジトリ](https://github.com/y-hirakaw/mcp-gh-pr-mini)

これを導入するとリポジトリ名などの指定は必要になります「PR作って」とAIエージェントに指示するだけで作ってくれるようになります。

## 実装手順

### 1. プロジェクトのセットアップ

まず、Node.jsとTypeScriptの環境をセットアップします。

* 新しいディレクトリを作成し、package.jsonを初期化します。

```bash
mkdir mcp-gh-pr-mini
cd mcp-gh-pr-mini
npm init -y
```

* 必要なパッケージをインストールします。

```bash
npm install --save @modelcontextprotocol/sdk @octokit/rest zod
npm install --save-dev typescript @types/node
```

* TypeScriptの設定ファイルtsconfig.jsonを作成し、以下の内容を記述します。

```
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
```

### 2. MCPサーバーの実装

src/index.tsにMCPサーバーの実装を行います。以下は、プルリクエストを作成するツールを定義する例です。

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { Octokit } from "@octokit/rest";

// サーバのインスタンス作成
const server = new McpServer({
  name: "mcp-gh-pr-mini",
  version: "1.0.0",
  capabilities: {
    resources: {},
    tools: {},
  },
});

// MCPで利用できるツールを定義
server.tool(
  // ツール名: ツール名は機能を端的に表すものにします。可能であれば動詞＋目的語で「何をするか」がひと目で分かる名前が望ましいです。
  "create_pull_request",
  // 詳細説明: ツールが「何をするか」「いつ有用か」を記述します。
  // 説明はモデルがツール選択を判断する重要な手掛かりとなります。
  // 基本英語が望ましいみたいですが、私は日本語併記してます。ツール一覧でも読みやすいためです。
  // 英語のみ、日本語併記、どちらでも動作的には変わりませんでした。
  "Create a new pull request in a GitHub repository（GitHubリポジトリで新しいプルリクエストを作成する）",
  // 入力パラメータのスキーマ設計: ツールが取る引数です。ここは必要最小限にして名前も直感的にします。
  {
    owner: z.string().describe("Repository owner (username or organization)"),
    repo: z.string().describe("Repository name"),
    title: z.string().describe("Pull request title"),
    body: z.string().describe("Pull request description"),
    head: z.string().describe("Name of the branch where your changes are implemented"),
    base: z.string().describe("Name of the branch you want the changes pulled into")
  },
  async ({ owner, repo, title, body, head, base }) => {
    try {
      const url = `${GITHUB_API_BASE}/repos/${owner}/${repo}/pulls`;
      const prData = await githubRequest<PullRequest>(url, {
        method: "POST",
        body: {
          title,
          body,
          head,
          base
        }
      });

      // 出力コンテンツ形式: モデルにとって扱いやすい形式で結果を返します。
      // 標準的には content: [{ type: "text", text: "・・・" }] のようにテキストを返すのがシンプルです。
      return {
        content: [
          {
            type: "text",
            text: `Pull request created successfully!\n\nPR #${prData.number}: ${prData.title}\nURL: ${prData.html_url}`,
          },
        ],
      };
    } catch (error) {
      console.error("Error creating pull request:", error);
      return {
        content: [
          {
            type: "text",
            text: `Failed to create pull request: ${error instanceof Error ? error.message : String(error)}`,
          },
        ],
      };
    }
  }
);

async function main() {
  // MCPサーバーの起動
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("GitHub MCP Server running on stdio");
}

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

このコードでは、@modelcontextprotocol/sdkを使用してMCPサーバーを作成し、@octokit/restを使用してGitHub APIと連携しています。省略してますが、リクエスト周りなどはお手数ですが実際のソースコード見てください。

必要以上に細かく大量のツールを用意しないことも重要です。モデルが混乱しないようにある程度汎用的に使えるツールにまとめる方が良いでしょう。「少数の強力なツール」にフォーカスすることで、組み合わせて様々な要求に対応できます。

個人的には思ったより少ない記述のツール名と詳細説明で、AIエージェントが理解して使ってくれることに驚きました。検索キーワードのように色々なパターンの呼び出し方を記載する必要はありません。

あとこの記事書いてから思いましたが、リクエスト周りは`Octokit`でも良いかも

### 3. ビルドと実行

* package.json に以下の build スクリプトを追加します（bin にも忘れずに指定）

```json
{
  "bin": {
    "mcp-gh-pr-mini": "./build/index.js"
  },
  "scripts": {
    "build": "tsc && chmod 755 build/index.js"
  }
}
```

そしてビルドを実行

```bash
npm run build
```

これで build/index.js に実行ファイルが生成されます。

### 4. npx 対応のために npm 公開

この MCP サーバーを npx で実行できるようにしたい場合は、npm に公開する必要があります。
※build/index.jsを直接指定しても使えるのでデバッグはそっちでやりました。

* npm にログイン

```bash
npm login
```

* 公開設定の確認

公開対象のファイルを package.json の files フィールドで指定

```json
"files": [
  "build"
]
```

* 公開

```bash
npm publish --access public
```

* 公開に成功すると他の人も以下のように実行できるようになります

```bash
npx mcp-gh-pr-mini
```

### 5. VSCodeのCopilot Agentなどから使う設定

`settings.json`に次のように記述すると、MCPサーバーをCopilot Agentで使えるようになります：

```json
"mcp": {
  "servers": {
    "mcp-gh-pr-mini": {
      "command": "npx",
      "args": ["mcp-gh-pr-mini"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "your_token_here"
      }
    }
  }
}
```

※利用にはGitHubのfine-grained アクセストークンが必要です。必要な権限は以下です。
* Pull requests: Read and write
* Issues: Read and write
* Contents: Read and write

## 最後に

MCPサーバーは「LLM × ツール実行」の世界をぐっと広げてくれる技術です。
AIが本当に開発のアシスタントとして振る舞うには、このような「ツールとの接続部分」を整備することが鍵になります。

この記事がMCPに興味がある方やAIと開発環境をもっと連携させたい方の参考になれば嬉しいです！