
はじめに
こんにちは!フロントエンドエンジニアの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 サーバーを構築するための公式 SDKzod— 入力値の型定義・バリデーションaxios— GitHub API への HTTP リクエスト
④ 開発用ライブラリのインストール
npm install -D typescript @types/node tsx
typescript/@types/node— TypeScript と Node.js の型定義tsx—tscでビルドせずに 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にご興味頂いた方は、是非下記のリンクからご応募ください。