KENTEM TechBlog

建設業のDXを実現するKENTEMの技術ブログです。

TypeScriptで自作するMCPサーバー超入門:GitHub API連携編

はじめに

こんにちは!フロントエンドエンジニアのH.Rです。
4月で早くも3年目。エンジニアとしての視座が変わってくる時期ですが、本当に月日が流れるのは早いものですね、、、

最近、AI界隈で大きな注目を集めているのが MCP(Model Context Protocol) だと思います。

ClaudeなどのAIツールに既存のサーバーを連携させるだけでも十分便利ですが、「AIがどうやって外部データにアクセスしているのか?」という裏側の仕組みを理解するには、自作してみるのが一番の近道です。

そこで今回は、学習の第一歩として GitHub API を活用したシンプルなMCPサーバーを一緒に構築していきましょう!!


前提条件

本記事のハンズオンを進めるにあたり、以下の準備をお願いします。

  • Node.js環境: v18以上(npm コマンドが使えること)
  • GitHub Personal Access Token (PAT): APIを叩くために必要です。GitHubのDeveloper settings から作成しておいてください。
  • MCPクライアント: 作成したサーバーをテストするため、VS Code や Cursor などがインストール済みであること。


開発環境の準備

まずはプロジェクトを初期化し、必要なライブラリをインストールします。今回はビルドの手間を省き、TypeScriptを直接実行できる tsx も導入します。

① プロジェクトフォルダーの作成と移動

mkdir my-github-mcp
cd my-github-mcp

② npm の初期化package.json を自動生成)

npm init -y

③ 本番依存ライブラリのインストール

npm install @modelcontextprotocol/sdk zod axios
  • @modelcontextprotocol/sdk — MCP サーバーを構築するための公式 SDK
  • zod — 入力値の型定義・バリデーション
  • axios — GitHub API への HTTP リクエスト

④ 開発用ライブラリのインストール

npm install -D typescript @types/node tsx
  • typescript / @types/node — TypeScript と Node.js の型定義
  • tsxtsc でビルドせずに TypeScript ファイルを直接実行できるツール

最新のMCP SDKはESM(ES Modules)での動作が基本となるため、package.json"type": "module" を追記しておきましょう。

// package.json に追記
{
  "type": "module",
  // ...
}


MCPサーバーの実装

作成したフォルダー直下にindex.ts を作成し、コードを書いていきます。

サーバーの基本構成とツールの定義

まずはサーバーを立ち上げ、AIに「こんなツールが使えるよ」と教える部分を定義します。 ここで定義する description は、AIが「いつこのツールを使うべきか」を判断する重要なプロンプトになります。

// MCP SDK、通信用のトランスポート、HTTPクライアント、スキーマバリデーションをインポート
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import axios from 'axios';
import { z } from 'zod';

/**
 * MCPサーバーの初期化
 * サーバー名とバージョンを定義します
 */
const server = new McpServer({
  name: 'my-github-analyzer',
  version: '1.0.0',
});

/**
 * 'get_repo_issues' ツールの登録
 * LLMがこのツールを呼び出す際のインターフェースを定義します
 */
server.registerTool(
  'get_repo_issues',
  {
    // ツールの説明(LLMがいつこのツールを使うべきか判断するために使用)
    description: '指定されたリポジトリの最新Issue一覧を取得',
    // 入力パラメータのバリデーションスキーマ
    inputSchema: {
      owner: z.string().describe('リポジトリの所有者'),
      repo: z.string().describe('リポジトリ名'),
    },
  },
  // ツール実行時の処理ロジック
  async ({ owner, repo }) => {
    try {
      // GitHub APIへリクエストを送信 (最新の5件を取得)
      const response = await axios.get(`https://api.github.com/repos/${owner}/${repo}/issues?per_page=5`, {
        headers: { 
          // 環境変数からGitHubトークンを読み込み
          Authorization: `token ${process.env.GITHUB_TOKEN}` 
        },
      });

      // 必要な情報(タイトル、番号、ステータスなど)のみを抽出して整形
      const issues = response.data.map((issue: any) => ({
        title: issue.title,
        number: issue.number,
        state: issue.state,
        url: issue.html_url,
        // ボディは長くなりすぎないよう最初の100文字に制限
        body: issue.body?.slice(0, 100),
      }));

      // 成功レスポンスを返却
      return {
        content: [{ type: 'text', text: JSON.stringify(issues, null, 2) }],
      };
    } catch (error: any) {
      // エラーハンドリング: LLMにエラーが発生したことを通知
      return {
        content: [{ type: 'text', text: `エラーが発生しました: ${error.message}` }],
        isError: true,
      };
    }
  },
);

/**
 * サーバーの起動メイン関数
 * 標準入出力(stdio)を介してクライアントと通信を開始します
 */
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  // サーバー起動ログ(標準エラー出力に出すことで、標準入力/出力の通信を邪魔しません)
  console.error('MCP Server is running on stdio');
}

// プロセスの開始
main().catch(console.error);

【解説】:コードの各処理について

① import(使用ライブラリ)

  • McpServer — MCPサーバーの本体。ツールの登録やリクエスト処理を一手に担います。
  • StdioServerTransport — 標準入出力(stdio)経由でAIクライアントと通信するトランスポート層です。VS Code や Cursor はこのstdioを通じてサーバーと会話します。
  • axios — HTTPクライアント。GitHub APIへのGETリクエストに使用します。
  • z(Zod) — スキーマ定義・バリデーションライブラリ。AIから渡される入力値の型を安全に扱います。

② サーバーのインスタンス化

new McpServer({ name, version }) でサーバーを作成します。name はAIクライアント側に表示されるサーバーの識別名です。


③ ツールの定義 server.registerTool()

registerTool(ツール名, { description, inputSchema }, ハンドラ関数) の3引数で構成されます。

  • ツール名(第1引数): AIが「このツールを使いたい」と指定するときの識別子です。
  • description: 単なる説明文ではなく、AIが「いつ・なぜこのツールを使うべきか」を判断するプロンプトです。具体的に書くほど、AIが自律的に適切なタイミングで呼び出してくれるようになります。
  • inputSchema: Zodスキーマでツールへの入力を定義します。.describe() で各フィールドにヒントを付けると、AIが正しい値を推論しやすくなります(例:「リポジトリ名だけ渡す」「URLではなく名前だけ」など)。
  • ハンドラ関数(第3引数): ツールが呼ばれたときの実処理です。inputSchema の型がそのまま推論されるため、別途バリデーションは不要です。

④ GitHub APIリクエストとレスポンス整形

axiosでGitHub APIを叩き、取得したIssueデータを必要なフィールドだけに絞り込んでいます。全データをそのまま返すとトークンを大量消費するため、title / number / state / url / body(先頭100文字)に絞ることで、AIへの返却データを軽量化しています。

MCPツールのレスポンスは必ず { content: [{ type, text }] } の形式で返す必要があります。


⑤ エラーハンドリング

エラーが起きてもそのまま isError: true とメッセージをAIに返しています。こうすることで、AI自身が「リポジトリ名が間違っているのでは?」と判断して自己修正を試みてくれます。エラーを握りつぶさないことが重要です。


⑥ サーバーの起動 main()

StdioServerTransport を生成して server.connect() に渡すことで、AIクライアントからのリクエストを受け付ける状態になります。console.error を使っているのは、stdoutがAIとの通信に使われているため、ログはstderrへ逃がす必要があるからです。


動作確認

VS Code や Cursor の設定ファイル(mcpServers)に、作成したサーバーと環境変数を追加します。 今回は tsx を使っているので、tsc でビルドしなくても直接TypeScriptファイルを実行できます!

"mcpServers": {
  "my-github-mcp": {
    "command": "npx",
    "args": ["tsx", "/絶対パス/my-github-mcp/index.ts"],
    "env": {
      "GITHUB_TOKEN": "ghp_xxxx_your_token_here_xxxx"
    }
  }
}

設定を保存してエディタ(VS Code または Cursor)を再起動します。

実際にAIに 「〇〇のリポジトリで今何が起きてる?重要そうなIssueを3つ教えて」 と聞くと、自作したツール経由で最新情報を取ってきて要約してくれます。


まとめ

今回は勉強目的でシンプルなAPIを叩くだけのものでしたが、自作の感覚は掴めたのではないでしょうか。

「必要な情報だけに絞ってトークンを節約する」「エラーをAIに読ませて自己解決させる」など、ちょっとした工夫でAIの動きは面白いくらいに変わります。

次は、「社内ドキュメント」などをMCP化するなど、自分なりのツール拡張に挑戦してみてください!

終わりに

KENTEMでは、様々な拠点でエンジニアを大募集しています! 建設×ITにご興味頂いた方は、是非下記のリンクからご応募ください。

recruit.kentem.jp

career.kentem.jp