3行まとめ
- 1MCP client
許可されたtoolだけを呼び出します。
- 2read-only MCP server
Issue、Pull Request、repository contentsをGETだけで扱います。
- 3GitHub REST API
Fine-grained PATで許可されたrepositoryと権限の範囲だけを読みます。
- 4対象外
Issue作成、PRコメント、file update、workflow起動は含めません。
read-onlyはGitHub上の状態を変えない境界であり、秘密情報や未信頼入力への注意は別に必要です。
GitHub連携MCPは、最初からIssue作成、PRコメント、ファイル更新まで渡すより、Issue、Pull Request、repository contentsを読むだけのread-only構成から始める方がレビューしやすいです。
ただし、read-onlyは「GitHub上の状態を変更しない」という意味であって、秘密情報の露出、未信頼入力、過剰なrepository閲覧、rate limit、監査不足の問題は残ります。
この記事では、2026-06-04に公式情報を確認したうえで、@modelcontextprotocol/sdk 1.29.0、GitHub REST API、Fine-grained personal access tokenを前提に、TypeScriptで最小のread-only MCPサーバーを組む判断軸をまとめます。
この記事で作るもの
作るのは、ローカルのstdioで起動するTypeScript製MCPサーバーです。MCPクライアントから呼べるtoolは、GitHub REST APIのGET系だけに絞ります。
github_list_issuesgithub_get_issuegithub_list_pullsgithub_get_pullgithub_get_contents
この5つを上限の目安にします。汎用のgithub_requestは作りません。便利ですが、method、endpoint、owner/repo、pathの境界が一気に曖昧になるからです。
この記事で作らないもの
Issue作成、PRコメント、merge、file update、GitHub Actions dispatch、Webhook受信、remote MCP hosting、OAuth app化、GraphQL対応は扱いません。
特に、POST/PATCH/DELETEを含む連携は別記事で扱うべき範囲です。この記事の目的は「AIエージェントにGitHubを読ませるとき、どこまで狭く始められるか」を判断することです。
この記事でわかること
対象repositoryとContents、Issues、Pull requestsのread権限を決めます。
MCPで公開するtool名を少数に固定します。
repository contentsはpath allowlistでさらに絞ります。
403、404、rate limit、token未設定、巨大ファイルを区別して扱います。
読後のゴールは、read-only GitHub MCPを社内PoCへ入れる前の判断順序を持つことです。
この記事で持ち帰れるのは、コード断片そのものよりも、read-only GitHub MCPを社内PoCに入れる前の設計順序です。
- Fine-grained PATで最初に絞るべきrepositoryと権限
- MCPのtool名を少数に固定する理由
- GitHub REST APIをGET endpointだけに包む実装方針
- repository contentsを読むときのpath allowlist
- 監査ログに残す情報と残さない情報
- 403、404、rate limit、token未設定、巨大ファイルなどの失敗条件
- read-onlyでも人間レビューを外さない方がよい場面
MCP自体の基礎から確認したい場合は、先に<a href="https://ai-dev.blog.mo-gmo.com/mcp-tools-resources-prompts-permission-design/">MCPとは何か:Tools・Resources・Promptsと権限設計</a>を読むと、Tools、Resources、Promptsの役割を整理しやすくなります。
前提知識
クライアントが利用できるtool一覧を取得します。
指定されたtoolをinput schemaに沿って実行します。
tool名、description、input schema、handlerに権限設計が表れます。
この記事では@modelcontextprotocol/sdk 1.29.0を基準にします。
GitHub連携では、toolを増やすほどAIが触れられる情報面も広がります。
MCPのToolは、AIエージェントが外部操作や外部取得を呼び出す入口です。公式のTools仕様では、クライアントがtools/listでtool一覧を取得し、tools/callで指定toolを実行する流れになります。
つまり、MCPサーバーのtool定義は「AIに何をさせるか」の境界そのものです。GitHub連携であれば、tool名、description、input schema、handlerの中に、権限設計がそのまま表れます。
read-onlyは安全の完成形ではない
read-only MCPは、GitHub上の状態を書き換えません。これは大きな利点です。PRコメントを書かない、Issueを作らない、ファイルを更新しない、workflowを起動しない、という境界を作れるからです。
一方で、Issue本文、PR本文、README、設計書、設定ファイルは、LLMに渡る未信頼入力です。本文の中に「これまでの指示を無視して」型の文章が混ざっていても、MCPサーバーはそれを「上位指示」として扱ってはいけません。詳しくは<a href="https://ai-dev.blog.mo-gmo.com/ai-coding-agent-prompt-injection-issues-docs-mcp-guardrails/">AIコーディングエージェントのprompt injection対策</a>で整理しています。
今回はnpm stableのSDKを基準にする
2026-06-04時点で、npm view @modelcontextprotocol/sdk versionは1.29.0でした。一方、公式GitHub repositoryのmainブランチ上のpackage.jsonは2.0.0-alpha.0でした。
この記事では、読者が再現しやすいようにnpm stableの@modelcontextprotocol/sdk@1.29.0を基準にします。mainブランチのalpha情報は、更新確認の対象として扱い、本文コードの前提にはしません。
npm view @modelcontextprotocol/sdk version
記事内のサンプルは、Node.jsのESM、TypeScript、zod/v4、stdio transportを想定します。社内標準がCommonJSや別のruntimeに寄っている場合は、importと実行コマンドを調整してください。
なぜGitHub MCPをread-onlyから始めるのか
セキュリティ上の比較なので勝敗ではなく、初期PoCで説明しやすい境界から選びます。
GitHubは、AIコーディングエージェントにとって非常に強い情報源です。Issue、PR、diff、README、コード、設定、CIログに触れられると、調査や修正提案の質が上がります。
その反面、権限を広く渡すと、AIエージェントは開発チームの作業面に深く入り込みます。最初のPoCでは、便利さよりも境界の説明しやすさを優先した方が、レビュー、導入判断、停止判断が楽になります。
| 段階 | GitHubへの副作用 | 人間承認 | ログ粒度 | 最初のPoC適性 |
|---|---|---|---|---|
| read-only | なし | tool許可とログレビューから始めやすい | 何を読んだかを中心に残す | 高い |
| dry-run | 直接の変更はないが変更案を生成する | 変更案の確認が必要 | 生成した差分や対象を残す | 中程度 |
| write | Issue作成、コメント、pushなどがあり得る | 操作前承認が必須 | 誰が何を変えたかまで必要 | 低い |
GitHub連携は便利さより権限境界が先に問題になる
X/Twitter上でもMCP、ブラウザ操作、agent権限、GitHub連携に近い話題は継続的に見られます。ただし、2026-06-04時点では、指定された2アカウントの直近72時間投稿を公開検索から安定して取得できませんでした。この記事ではXを事実ソースに使わず、需要シグナルとして「MCPと外部tool権限への関心が続いている」程度に留めます。
実務で重要なのは、話題性ではなく、次の問いに答えられることです。
- このMCPサーバーは、どのowner/repoを読めるのか
- どのGitHub REST endpointを呼べるのか
- tokenにどのrepository permissionを与えたのか
- AIに渡した本文は、未信頼入力として扱われるのか
- 後から「何を読もうとしたか」を追えるのか
read-onlyでもIssueやdocsは未信頼入力である
read-only MCPで一番見落としやすいのは、取得対象が「読むだけのデータ」ではなく、AIへの入力になる点です。
Issue本文には、ユーザー、外部協力者、bot、過去の開発者が書いた文章が混ざります。READMEやdocsにも、古い運用手順やサンプルの秘密情報が残っていることがあります。AIエージェントに読ませるなら、次のように扱うべきです。
- GitHubから取った文章は参考情報であり、上位指示ではない
- Issue本文の指示より、リポジトリ内のAGENTS.mdやシステム側ルールを優先する
- file contentsを丸ごとログに残さない
.env、秘密鍵、credential、production dumpのようなpathは読ませない- allowlist外のrepositoryやpathは、AIが要求しても拒否する
注意点
read-onlyは副作用を小さくする設計であって、情報管理を不要にする設計ではありません。
最小構成の全体像
toolを定義し、stdioでクライアントと接続します。
GitHub APIへGETだけを送り、methodとheadersを固定します。
owner、repo、pathを検証し、許可対象以外をAPIへ渡しません。
読み取り試行と結果を残し、tokenや本文全量は残しません。
remote公開やOAuth化より先に、ローカルで境界を説明できる形にします。
今回の構成は4つに分けて考えると、レビューしやすくなります。
| 部品 | 役割 | 境界 |
|---|---|---|
| MCP server | toolを定義し、stdioでクライアントとつなぐ | 公開toolを少数に固定する |
| GitHub REST client | GitHub APIへGETだけを送る | methodとheadersを固定する |
| Allowlist | owner/repo/pathを検証する | 許可対象以外をAPIへ渡さない |
| Audit log | 読み取り試行と結果を残す | tokenや本文全量は残さない |
stdioローカル実行に閉じる
最初のPoCでは、remote MCP serverとして公開しない方が扱いやすいです。stdio transportなら、MCPクライアントがローカルコマンドとしてサーバーを起動します。HTTP公開時に必要な認証、CORS、ネットワーク境界、セッション管理、サーバー監視を後回しにできます。
TypeScript SDKの最小構成は、既存の<a href="https://ai-dev.blog.mo-gmo.com/typescript-mcp-server-tools-resources-prompts-minimal/">TypeScriptでMCPサーバーを作る記事</a>でも扱っています。今回はその上に、GitHub REST API、token、allowlist、監査ログを足すイメージです。
GitHub API clientはGETだけにする
実装上は、tool handlerごとにfetchを書くより、GitHub用の小さなclientを作り、そこにGET固定の制約を置く方が安全です。
type GitHubGetOptions = {
path: string;
token: string;
query?: Record<string, string | number | undefined>;
};
async function githubGet({ path, token, query = {} }: GitHubGetOptions) {
const url = new URL(`https://api.github.com${path}`);
for (const [key, value] of Object.entries(query)) {
if (value !== undefined) url.searchParams.set(key, String(value));
}
const response = await fetch(url, {
method: "GET",
headers: {
"Accept": "application/vnd.github+json",
"Authorization": `Bearer ${token}`,
"X-GitHub-Api-Version": "2022-11-28",
"User-Agent": "ai-dev-lab-github-readonly-mcp",
},
});
return response;
}
この関数にmethod引数を持たせないのがポイントです。将来write系を追加するなら、別関数、別tool、別レビューにします。
確認コマンド
最小構成の確認は、成功ケースより先に境界を見ます。
node --version
npm view @modelcontextprotocol/sdk version
npm install @modelcontextprotocol/sdk@1.29.0 zod
npm install -D typescript tsx @types/node
次に、GITHUB_TOKEN未設定時に起動を止める、allowlist外repoを拒否する、allowlist外pathを拒否する、という失敗ケースを通します。
Fine-grained PATはrepoと権限を小さく作る
Fine-grained PATはGitHub側の境界であり、MCPサーバー側のallowlistとは別に確認します。
GitHub REST APIをprivate repositoryに対して使うなら、認証情報が必要です。この記事では、最初のPoCとしてFine-grained personal access tokenを使う前提で説明します。
GitHub AppsやOAuth Appが適している組織もありますが、最小構成の学習とPoCでは、Fine-grained PATの方が「どのrepoに、どの権限を与えたか」を確認しやすいです。
Resource ownerとRepository accessを先に絞る
Fine-grained PATでは、resource ownerとrepository accessを選びます。最初から全repositoryを対象にせず、検証用の1repositoryに限定してください。
設定の初期値
| 項目 | 推奨する初期値 | 理由 |
|---|---|---|
| Resource owner | 検証対象のuserまたはorganization | どの所有者のrepositoryを読めるかを固定する |
| Repository access | Only selected repositories | 対象repoを1つから始める |
| Expiration | 短めに設定 | PoC後の放置を避ける |
| Token name | 用途がわかる名前 | 監査と棚卸しをしやすくする |
organization repositoryでは、組織側の承認ポリシーによりtokenがすぐ使えない場合があります。ここで詰まったら、MCP実装の問題ではなくGitHub側の認可設定の問題として切り分けます。
Issues、Pull requests、Contentsはreadに限定する
今回のtoolに必要なrepository permissionは、最小では次の3つです。
| GitHub RESTで読む対象 | 使うendpoint例 | Fine-grained PAT権限 |
|---|---|---|
| Issue一覧/詳細 | GET /repos/{owner}/{repo}/issues、GET /repos/{owner}/{repo}/issues/{issue_number} | Issues: Read |
| Pull Request一覧/詳細 | GET /repos/{owner}/{repo}/pulls、GET /repos/{owner}/{repo}/pulls/{pull_number}、GET /repos/{owner}/{repo}/pulls/{pull_number}/files | Pull requests: Read |
| ファイル内容 | GET /repos/{owner}/{repo}/contents/{path} | Contents: Read |
GitHub DocsのFine-grained PAT権限表では、endpointごとに必要な権限を確認できます。公開前にこの表を見直すのが大事です。GitHub側の権限表は変わり得るため、社内手順書に落とす場合も確認日を残してください。
Metadataは見える範囲として扱う
GitHubのAPIでは、基本的なrepository metadataが関係する場面があります。ここを「何か足りないから権限を全部readにしよう」と誤解しないようにします。
権限不足でAPIが失敗したときは、response headerのX-Accepted-GitHub-Permissionsや対象endpointのドキュメントを確認し、必要な権限だけを増やします。最初からContents、Issues、Pull requests以外を広く付ける必要はありません。
注意点:Contents readは強い
Contents readは便利ですが、強い権限です。対象repositoryに秘密情報が置かれていれば、読めてしまう可能性があります。
GitHub上に秘密情報を置かないのが前提ですが、MCP側でもpath allowlistで補完します。
README.mddocs/src/package.jsontsconfig.json
たとえば最初はこの程度に絞り、.env、.pem、.key、secrets/、dump/、backup/、node_modules/、dist/、巨大ファイル、binary fileは拒否します。
Tool allowlistはMCPの外側と内側で二重に置く
- 1client許可
利用者環境で呼べるtoolを絞ります。
- 2公開tool名
github_list_issuesなど、用途が読める少数のtoolに固定します。
- 3repo検証
owner/repoをserver側で確認し、対象外はAPIへ送る前に拒否します。
- 4path検証
contents取得はdocs/やREADME.mdなど、許可したpathだけに絞ります。
クライアント設定は利用者環境に依存するため、サーバー側の制約を最後の境界として持ちます。
MCPクライアント側にtool許可設定がある場合でも、サーバー側のallowlistは必要です。クライアント設定は利用者環境に依存します。サーバー側の制約は、実装として常に効く最後の境界です。
公開するtool名を少数に固定する
tool名は、権限レビューの単位です。名前だけで用途が読めるようにします。
| tool名 | 目的 | 作らない代替 |
|---|---|---|
github_list_issues | Issue一覧を読む | github_search_everything |
github_get_issue | Issue詳細を読む | github_request |
github_list_pulls | PR一覧を読む | github_api |
github_get_pull | PR詳細を読む | github_mutate_pull |
github_get_contents | 許可pathのファイルを読む | github_get_any_file |
汎用toolは、使う側には便利です。しかし、監査する側から見ると「何ができるtoolなのか」を毎回handlerの中まで読まなければいけません。AIエージェント向けのtoolは、狭く、名前で意図がわかる方が扱いやすいです。
owner/repoをserver側で検証する
owner/repoは、MCPクライアントやLLMから渡された文字列をそのままAPI URLに入れないでください。環境変数や設定ファイルで許可repositoryを固定し、入力値が一致した場合だけGitHub APIへ進めます。
const allowedRepos = new Set(
(process.env.GITHUB_ALLOWED_REPOS ?? "")
.split(",")
.map((repo) => repo.trim())
.filter(Boolean),
);
function assertAllowedRepo(owner: string, repo: string) {
const key = `${owner}/${repo}`;
if (!allowedRepos.has(key)) {
throw new Error(`Repository is not allowed: ${key}`);
}
return key;
}
失敗時のtool resultでは、詳細な内部設定を返さず、「このrepositoryはallowlist外です」と短く返します。監査ログには、試行されたowner/repo、tool名、時刻、拒否理由を残します。
path allowlistでcontents取得をさらに絞る
GET /repos/{owner}/{repo}/contents/{path}は、使い方を間違えるとrepository内の広い範囲をAIに渡します。pathもserver側で絞ります。
失敗条件
const allowedPathPrefixes = ["README.md", "docs/", "src/", "package.json"];
const blockedPathFragments = [".env", ".pem", ".key", "secrets/", "backup/"];
function normalizeContentPath(input: string) {
const path = input.replace(/^\/+/, "");
if (!path || path.includes("..") || decodeURIComponent(path).includes("..")) {
throw new Error("Invalid path");
}
if (blockedPathFragments.some((fragment) => path.includes(fragment))) {
throw new Error("Blocked path");
}
if (!allowedPathPrefixes.some((prefix) => path === prefix || path.startsWith(prefix))) {
throw new Error("Path is not allowed");
}
return path;
}
URL encodeで..を回避しようとする入力もあるため、decode後も確認します。binary fileや大きすぎるfileは、GitHub APIのresponseを見てから拒否します。
GitHub REST APIはGET endpointだけを薄く包む
汎用のgithub_requestを作らず、endpointごとにレビューできる薄いtoolへ分けます。
toolとendpointの対応を表にしておくと、レビューが楽になります。
| tool名 | method | endpoint例 | 主な入力 | 失敗時の扱い |
|---|---|---|---|---|
github_list_issues | GET | /repos/{owner}/{repo}/issues | state、labels、per_page | 403/404/rate limitを分ける |
github_get_issue | GET | /repos/{owner}/{repo}/issues/{issue_number} | issue_number | 存在しない番号は短いエラーにする |
github_list_pulls | GET | /repos/{owner}/{repo}/pulls | state、base、head、per_page | PR read不足をログに残す |
github_get_pull | GET | /repos/{owner}/{repo}/pulls/{pull_number} | pull_number | files取得は別呼び出しでもよい |
github_get_contents | GET | /repos/{owner}/{repo}/contents/{path} | path、ref | path allowlistとsize上限を見る |
IssueとPull Requestは一覧と詳細に分ける
一覧toolは、AIが調査対象を絞るために使います。詳細toolは、番号指定で本文やmetadataを読むために使います。
レスポンスを丸ごと返す必要はありません。最初は次の程度に整えます。
numbertitlestateuser.logincreated_atupdated_athtml_urlbodyの先頭数千文字まで
bodyを短く切るのは、token節約だけでなく、未信頼入力の影響範囲を小さくする意味もあります。必要なら「本文の続きが必要か」を人間に確認させる運用にできます。
repository contentsはファイル取得を中心にする
最小構成では、directory listingよりfile取得を中心にした方が扱いやすいです。directory listingを許すと、AIが探索範囲を広げやすくなります。
file取得では、次を確認します。
- responseの
typeがfileか encodingがbase64かsizeが上限以内か- allowlist内pathか
- binary fileではないか
- refを許可するなら、branch/tag/shaの扱いを決めているか
最初はrefを省略し、default branchの許可pathだけにしても構いません。branch比較や過去commit参照は便利ですが、PoC初期の説明責任が増えます。
REST client層でheadersを固定する
GitHub REST APIでは、認証にAuthorization: Bearerを使い、API version headerとしてX-GitHub-Api-Versionを送る形が公式ドキュメントで案内されています。サンプルでは2022-11-28を明示します。
rate limitも早めに見ます。GitHub Docsでは、unauthenticated requestのprimary rate limitは60 requests/hour、personal access tokenなどのauthenticated requestは通常5,000 requests/hourとして説明されています。実運用ではsecondary rate limitもあるため、403をすべて権限不足と決めつけないでください。
TypeScript SDK 1.29.0でMCP serverを組み立てる
@modelcontextprotocol/sdkとZodの依存を固定します。
tool登録とstdio接続を持ち、descriptionにread-onlyの意図を入れます。
owner/repo/pathの許可条件を集中させます。
GitHub REST APIへのGET requestとheadersを扱います。
tool名、対象、status、request idをJSONLで残します。
LLM向けの短いエラーと、ログ向けの調査情報は分けて扱います。
最小のファイル構成は、次のように分けると読みやすくなります。
github-readonly-mcp/
package.json
tsconfig.json
src/
server.ts
github.ts
allowlist.ts
audit.ts
package.json
SDKとZodを固定します。ここでは、2026-06-04確認時点のstableである@modelcontextprotocol/sdk@1.29.0を使います。
{
"name": "github-readonly-mcp",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc -p tsconfig.json",
"dev": "tsx src/server.ts"
},
"dependencies": {
"@modelcontextprotocol/sdk": "1.29.0",
"zod": "^4.1.12"
},
"devDependencies": {
"@types/node": "^22.15.3",
"tsx": "^4.19.4",
"typescript": "^5.8.3"
}
}
server.tsの骨格
tool登録では、descriptionに「read-only」「GET only」「allowlisted repository only」を入れておくと、クライアント側のtool一覧でも意図が伝わります。
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod/v4";
const server = new McpServer({
name: "github-readonly-mcp",
version: "0.1.0",
});
server.registerTool(
"github_get_issue",
{
title: "Read GitHub issue",
description: "Read one issue from an allowlisted GitHub repository. GET only.",
inputSchema: {
owner: z.string().min(1),
repo: z.string().min(1),
issueNumber: z.number().int().positive(),
},
},
async ({ owner, repo, issueNumber }) => {
// ここでrepo allowlist、GitHub GET、audit logを呼ぶ
const issue = {
owner,
repo,
number: issueNumber,
note: "Replace this object with the normalized GitHub REST response.",
};
return {
content: [{ type: "text", text: JSON.stringify(issue, null, 2) }],
};
},
);
const transport = new StdioServerTransport();
await server.connect(transport);
実務用には、handler内へ直接ロジックを書き込まず、allowlist.ts、github.ts、audit.tsへ分けます。tool handlerは、入力検証済みの値を渡し、失敗時に安全なtool resultを返す薄い層にします。
エラーはLLM向けとログ向けに分ける
AIへ返すエラーは短くします。内部設定、token、GitHub response body全文、file content全文は返しません。
function toolError(message: string) {
return {
isError: true,
content: [{ type: "text" as const, text: message }],
};
}
監査ログには、もう少し調査しやすいmetadataを残します。
{
"at": "2026-06-04T09:30:00.000Z",
"tool": "github_get_contents",
"repo": "octo-org/example",
"path": "src/index.ts",
"method": "GET",
"status": 200,
"requestId": "github-request-id",
"rateLimitRemaining": "4987",
"result": "ok"
}
残すのは「何を読もうとしたか」と「結果を調査できるmetadata」です。本文全量を残すと、監査ログが別の情報漏えい源になります。
監査ログは「誰が何を読もうとしたか」を追える粒度にする
監査ログの目的は、何を読めたかよりも、拒否すべき入力が拒否されたかを確認することです。
read-only MCPで監査ログが必要なのは、成功を誇るためではありません。拒否すべきアクセスが拒否されたか、権限不足が起きたか、rate limitに当たったかを後から見直すためです。
成功ログより拒否ログを重視する
最初に見るべきログは、成功したGETではなく拒否ログです。
| イベント | 残す情報 | 残さない情報 |
|---|---|---|
| allowlist外repo | tool名、owner/repo、拒否理由 | token、許可repo一覧全量 |
| allowlist外path | tool名、repo、path、拒否理由 | file content |
| 権限不足 | status、GitHub request id、endpoint種別 | response body全文 |
| rate limit | status、remaining、reset、request id | token |
| 成功 | tool名、repo、path/number、status | Issue本文やfile content全量 |
ログはJSONLで1行1イベントにすると、後で集計しやすいです。保存期間、ローテーション、保存先、アクセス権も決めます。
GitHub request idとrate limit情報を残す
GitHub APIのresponse headerには、request idやrate limit関連の情報が含まれます。障害調査では役に立ちますが、LLMへ渡す必要はほとんどありません。
MCP tool resultには「GitHub APIがrate limitに達しました。しばらく待って再試行してください。」程度を返し、ログにはstatus、request id、remaining、resetを残す、と分けてください。
tokenと本文全量をログに残さない
監査ログへtokenを残さないのは当然として、Issue本文やfile content全量も避けます。read-only MCPは情報を読むためのサーバーです。ログが巨大な二次保管庫になると、別の管理対象が増えてしまいます。
必要なら、本文のhash、文字数、先頭の短い抜粋だけを残します。抜粋を残す場合も、個人情報や秘密情報が入る可能性を考えて、最初のPoCではmetadataだけにするのが無難です。
失敗条件を先に決めてから動作確認する
失敗条件を先に決めると、PoCが単なる成功デモで終わりにくくなります。
GitHub連携MCPの検証で、成功デモだけを見ると危険です。実務で重要なのは、失敗すべき入力が失敗することです。
tokenと権限の失敗
まず、認証情報まわりを落とします。
| 条件 | 期待する結果 | 見る場所 |
|---|---|---|
GITHUB_TOKEN未設定 | 起動時または初回callで安全に失敗 | tool result、stderr、audit log |
| token期限切れ | 認証失敗として返す | status、request id |
| 対象repo外 | GitHub APIへ行く前に拒否 | audit log |
| Issues read不足 | Issue toolだけ失敗 | status、accepted permissions |
| Pull requests read不足 | PR toolだけ失敗 | status、accepted permissions |
| Contents read不足 | contents toolだけ失敗 | status、accepted permissions |
ここで、すべての失敗を「tokenが間違っています」にまとめない方がよいです。権限不足、repo対象外、rate limit、存在しないresourceは、運用上の意味が違います。
GitHub RESTの失敗
GitHub APIでは、403や404が複数の意味を持つことがあります。private repoで権限が足りない場合、見え方が直感と違うこともあります。
失敗時は、本文ではなくmetadataで判断します。
- HTTP status
- GitHub request id
- rate limit remaining/reset
X-Accepted-GitHub-Permissions- 呼び出したtool名
- owner/repo/pathまたはnumber
403を「権限不足」と決め打ちせず、rate limitとsecondary rate limitも候補に入れてください。
入力とcontentsの失敗
repository contentsでは、入力検証が特に大事です。
- allowlist外path
..を含むpath- URL encodeで
..を含むpath - 先頭
/でroot風に見せるpath - 巨大ファイル
- binary file
- directoryをfileとして扱うケース
- base64 decode失敗
成功ケースより先に、これらが拒否されることを確認してください。
結果:最初のPoCで見るべき合格ライン
tools/listにGitHub read-only toolだけが出ます。
すべてのtoolがGET endpointだけを呼びます。
allowlist外のowner/repoはGitHub APIへ行く前に拒否されます。
token未設定、権限不足、rate limit、404を分けて扱えます。
tool名、repo、path/number、status、request idが残ります。
うまくいかなかったときは、tool一覧、allowlist、PAT、REST response、audit logの順に見ます。
この記事の構成で最初のPoCを行うなら、合格ラインは次の通りです。
評価基準
- MCPクライアントの
tools/listに、GitHub read-only toolだけが出る。 - すべてのtoolがGET endpointだけを呼ぶ。
- allowlist外のowner/repoはGitHub APIへ行く前に拒否される。
- contents取得は許可pathだけに限定される。
- token未設定、権限不足、rate limit、404を区別して扱える。
- audit logにtool名、repo、path/number、status、request idが残る。
- token、Issue本文全量、file content全量がログに残らない。
ここまで通れば、「AIにGitHubを読ませる」PoCとしてはかなり小さく始められます。逆に、ここが通らない状態でwrite系toolを足すのは早いです。
うまくいかなかったときに見る順番
動かないときは、次の順番で見ます。
npm view @modelcontextprotocol/sdk versionで記事と同じstableを見ているか。- MCP serverがstdioで起動しているか。
GITHUB_TOKENが環境変数として渡っているか。- Fine-grained PATのrepository accessが対象repoを含むか。
- Issues、Pull requests、Contentsのread権限が足りているか。
- owner/repo/path allowlistで弾いていないか。
- GitHub APIのrate limitやsecondary rate limitに当たっていないか。
SDKやGitHub権限の仕様は変わるため、記事のコードをそのまま社内標準にせず、確認日とバージョンを手順に残してください。
実務で使うなら
- 個人PoC
対象repoを1つ、toolを5つ以下、GETのみ、stdioのみ、ログはJSONLにします。
- チームPoC
tokenの所有者、保管場所、review担当、ログ閲覧者を決めます。
- 運用前
権限レビュー、ログレビュー、token rotation、停止基準を確認します。
- 拡張時
write系やremote化は、承認と監査の設計を先に増やしてから検討します。
便利さを増やすほど、権限境界と人間レビューの設計も増やします。
個人PoC、チームPoC、本番運用で、見るべきものを分けます。
個人PoC
個人PoCでは、対象repoを1つ、toolを5つ以下、GETのみ、stdioのみ、ログはJSONL、という制約にします。
この段階では、便利さを増やすよりも、拒否条件を見る方が価値があります。AIに「このrepo以外を読んで」と頼んで拒否されるか、secrets/を読もうとして拒否されるか、token未設定で安全に失敗するかを確認してください。
チームPoC
チームPoCでは、tokenの所有者と保管場所が重要になります。
- 誰がFine-grained PATを作るのか
- organization repoでは承認が必要か
- tokenをどこに置くのか
- ローテーション周期はどうするのか
- 退職や異動時に誰が無効化するのか
- allowlist変更はPRレビューにするのか
- audit logを誰が見るのか
ここを曖昧にしたままMCPを配ると、read-onlyでも運用が属人化します。法人導入の権限設計は、<a href="https://ai-dev.blog.mo-gmo.com/enterprise-ai-coding-permission-design-api-keys-private-repos-human-approval/">APIキー、非公開リポジトリ、人間承認フローの記事</a>も参考になります。
本番運用へ広げる条件
本番運用へ広げるなら、少なくとも次を満たしてから対象repoを増やします。
- security reviewを通した
- tool allowlistの変更手続きがある
- path allowlistの変更手続きがある
- audit logの保存期間とアクセス権が決まっている
- token rotationが運用に入っている
- prompt injectionを未信頼入力として扱うルールがある
- write系toolは別設計、別承認にしている
read-only MCPは、write系へ進む前の土台として使うと強いです。最初から万能GitHub toolを作るより、読ませる範囲を説明できるMCPサーバーにしておく方が、チームでレビューしやすくなります。
セキュリティ・コスト注意
全repositoryや不要な権限を最初から渡さないようにします。
github_requestのような広いtoolでmethodやendpointを自由にしません。
Issue本文やfile content全量をそのまま返すと、contextと費用を使います。
拒否ログ、status、request idがないと、後から判断できません。
PATはGitHub側の権限、allowlistはMCPサーバー側の境界、監査ログは後から確認する証跡です。
セキュリティ面では、Fine-grained PAT、allowlist、監査ログの3つを分けて見ます。PATはGitHub側の権限、allowlistはMCPサーバー側の境界、監査ログは後から確認するための証跡です。どれか1つだけでは足りません。
コストの見方
コスト面では、GitHub API自体のrate limitと、LLM側のtoken消費を見ます。Issue本文やfile contentを丸ごと返すと、LLM側のcontextと費用を使います。body抜粋、file size上限、必要なfileだけの取得で抑えてください。
やってはいけない初期構成
最初の実装で避けたいのは、次の形です。
github_requestのような汎用toolを作る- tool引数で任意methodを受け取る
- tool引数で任意URLを受け取る
- owner/repo allowlistがない
- path allowlistがない
- tokenをログに出す
- response body全文を監査ログに残す
- 403をすべて権限不足と決めつける
- read-onlyだから人間レビュー不要、と扱う
AIエージェントに外部toolを渡すと、便利さはすぐ上がります。だからこそ、最初は退屈なくらい狭い構成で始める方が長持ちします。
よくある迷いどころ
個人利用や一時調査では早い一方、repoやpathの境界を明文化したい場合は専用実装も選択肢です。
柔軟ですが、最小構成ではREST GETの方がendpointと権限の対応を追いやすいです。
read-onlyでも未信頼入力を読むため、承認なしで広く自動実行する前に範囲を絞ります。
read-onlyは判断を軽くする材料であり、人間レビューを不要にする理由ではありません。
公式や既存のGitHub MCPではだめですか
既存サーバーを使う選択肢はあります。特に個人利用や一時的な調査では、すでに整備されたMCPサーバーの方が早いこともあります。
ただし、企業やチームで「このrepoの、このpathだけ読ませる」「監査ログにこの項目だけ残す」「write系は存在させない」といった境界を明文化したいなら、自作のread-only専用サーバーは選択肢になります。
GraphQLを使わない理由は何ですか
GraphQLは柔軟です。一方、最小構成ではREST GETの方が、endpointとFine-grained PAT権限の対応を追いやすいです。
この記事の目的は、GitHubから効率よくあらゆる情報を取ることではありません。toolごとの権限境界を読みやすくすることです。GraphQLは、read-only REST構成で運用ルールが固まってから検討しても遅くありません。
read-onlyなら承認なしで自動実行してよいですか
最初から完全自動にする必要はありません。read-onlyでも、private repoの情報、Issue本文、設計資料、file contentsをAIに渡します。
初期PoCでは、tool allowlist、repo allowlist、path allowlist、監査ログを確認しながら使い、チームの合意が取れた範囲だけ自動実行に寄せる方が安全です。
次に読むなら
MCPやGitHub権限まわりは更新が速い領域です。更新通知を追いたい場合は、<a href="https://ai-dev.blog.mo-gmo.com/newsletter/">ニュースレター</a>でAI開発基盤の記事更新を確認できます。自社のMCP設計、Fine-grained PAT、tool allowlist、監査ログを一緒に見直したい場合は、<a href="https://ai-dev.blog.mo-gmo.com/contact/">お問い合わせ</a>から相談してください。
参照した主な情報源
- https://modelcontextprotocol.io/docs/sdk – Model Context Protocol SDKs
- https://github.com/modelcontextprotocol/typescript-sdk – Model Context Protocol TypeScript SDK
- https://modelcontextprotocol.io/specification/latest/server/tools – Model Context Protocol Tools specification
- https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens – GitHub Docs: Managing your personal access tokens
- https://docs.github.com/en/rest/authentication/permissions-required-for-fine-grained-personal-access-tokens – GitHub Docs: Permissions required for fine-grained personal access tokens
- https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api – GitHub Docs: Authenticating to the REST API
- https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28 – GitHub Docs: Rate limits for the REST API
更新履歴
- 2026-06-04
@modelcontextprotocol/sdkのnpm stableが1.29.0であることを確認しました。
- 2026-06-04
TypeScript SDK repository mainが2.0.0-alpha.0であることを確認しました。
- 2026-06-04
GitHub REST API、Fine-grained PAT権限、REST API認証、rate limitの公式情報を確認しました。
SDKやGitHubの仕様は変わるため、実装時は現在の公式情報も確認します。
| 日付 | 内容 |
|---|---|
| 2026-06-04 | 初版。@modelcontextprotocol/sdkのnpm stableが1.29.0であること、TypeScript SDK repository mainが2.0.0-alpha.0であること、GitHub REST API、Fine-grained PAT権限、REST API認証、rate limitの公式情報を確認しました。 |
