本文へ移動
AI Dev Lab Japan AI開発ツール、AIコーディングエージェント、M...

TypeScriptでGitHub read-only MCP Serverを作る:Issues参照から始める権限設計

TypeScriptでGitHub read-only MCP Serverを作る:Issues参照から始める権限設計の判断ポイントを表す抽象サムネイル

AIコーディングエージェントにGitHubの文脈を渡すと、Issueの背景、未完了タスク、設計議論を読ませやすくなります。問題は、最初から便利な万能連携にすると、どのToolがどの権限で何を読めるのかが見えにくくなることです。

この記事では、2026年6月15日に公式ドキュメントとローカル検証で確認した範囲で、TypeScriptの最小MCP ServerからGitHub REST APIのIssues一覧をread-onlyで読む実装を作ります。@modelcontextprotocol/sdkはnpm latestとして確認した1.29.0を使い、GitHub側はREST API version 2022-11-28、fine-grained personal access token、Issues repository permission readを前提に整理します。

3行まとめ

VisualIssues read-only MCP Serverの要点最初に絞る範囲と、残るリスクを整理します。
最初のTool

list_repository_issuesのようなread only参照に閉じます。

最小権限

public repositoryはtokenなし検証、実務ではIssues readを候補にします。

残るリスク

Issue本文には社内URL、顧客情報、脆弱性メモが入り得ます。

設計の支え

allowlist、返却フィールド削減、ログ設計、人間承認を組み合わせます。

read onlyは副作用を小さくしますが、読ませる情報の範囲は別に管理します。

  • MCP ServerでGitHub連携を始めるなら、最初のToolはlist_repository_issuesのようなread-only参照に絞ると、権限、入力schema、失敗条件を分けて検証しやすいです。
  • GitHub Issuesの一覧取得は、fine-grained tokenではIssues repository permissionのreadが最小候補です。public repositoryだけならtokenなしでも試せますが、rate limitとprivate repository対応を考えると実務判断には足りません。
  • read-onlyでも安全とは言い切れません。Issue本文には社内URL、顧客情報、脆弱性メモ、プロンプト注入に近い指示文が入り得るため、allowlist、返却フィールド削減、ログ設計、人間承認をセットで考えます。

この記事でわかること

Visual読後に確認できる設計項目GitHub Issues参照を最小単位にして、導入前の判断軸を持ちます。
MCP Server

TypeScript SDKでstdioの最小構成を作る流れを確認します。

Tool contract

GitHub REST Issues APIをread onlyで呼ぶ入力と出力を決めます。

token設計

tokenなし、fine grained PAT、GitHub Appの使い分けを整理します。

失敗条件

401、403、404、rate limit、MCP schema errorを切り分けます。

拡張前の確認

write操作へ広げる前に承認、監査、権限分離を考えます。

MCP Serverの概念説明ではなく、GitHub Issues参照の実務導入を扱います。

  • TypeScript SDKでstdioの最小MCP Serverを作る流れ
  • GitHub REST Issues APIをread-onlyで呼ぶTool contractの決め方
  • tokenなし、fine-grained PAT、GitHub Appをどう使い分けるか
  • 401、403、404、rate limit、MCP schema errorをどう切り分けるか
  • read-only版からwrite操作へ広げる前に必要な承認、監査、権限分離

この記事はMCP Serverの概念説明ではなく、GitHub Issues参照を実務導入の最小単位として扱います。MCPのTools、Resources、Promptsの基本を先に押さえたい場合は、公開済みのMCPとは何か:Tools・Resources・Promptsと権限設計も合わせて読むと、この記事の判断軸が追いやすくなります。

前提知識

VisualMCP ToolからGitHub Issuesへ届く流れAI Host、MCP Server、GitHub APIの境界を分けます。
  1. 1Host

    AIアプリケーションがMCP ServerのToolを発見します。

  2. 2Tool

    model controlledな入口として、呼び出し可否を人間が判断できます。

  3. 3Transport

    ローカル最小検証ではstdioが扱いやすい選択肢です。

  4. 4GitHub REST API

    Issues一覧endpointを使い、必要な権限とrate limitを確認します。

  5. 5Response

    status、rate limit、取得件数を分けて確認します。

Toolを1つ追加することは、AIに外部システムへの新しい入口を渡すことです。

MCPでは、Host側のAIアプリケーションがMCP ServerのToolを発見し、必要に応じて呼び出します。MCP Tools仕様ではToolはmodel-controlledとして扱われ、信頼と安全性のために、人間がTool呼び出しを拒否できる設計が推奨されています。つまり、Toolを1つ追加することは、AIに外部システムへの新しい入口を渡すことでもあります。

TypeScript SDK 1.29.0のドキュメントでは、MCP ServerはMcpServerで作り、stdioやStreamable HTTPなどのtransportへ接続します。ローカルでAIコーディングエージェントから子プロセスとして起動する最小検証なら、stdioが一番扱いやすいです。リモート共有やチーム運用へ進むならStreamable HTTP、認証、セッション、デプロイ境界の話が増えます。

GitHub側は、Issues一覧のREST endpointを使います。公式ドキュメント上、このendpointはpublic resourcesだけなら認証なしでも使えますが、fine-grained tokenを使う場合はIssues repository permissionのreadが必要です。認証済みREST requestは通常の個人rate limitに入り、GitHubのrate limitドキュメントではauthenticated userのprimary rate limitが5,000 requests/hourと説明されています。今回の検証では、public repositoryのIssues取得をtokenなしで呼び、status=200x-ratelimit-limit=60items=2を確認しました。

結果: 最初はIssues read-only Toolに閉じる

Visual最初の接続で狭くする境界GitHubを何でも代理実行するのではなく、Issue一覧参照だけに絞ります。
Tool

list_repository_issuesだけを公開します。

Input

owner、repo、state、labels、per_pageに限定します。

Output

番号、タイトル、状態、ラベル、作者、更新日時、URL、短い抜粋に絞ります。

Review

コード行数よりも、AIが到達できる操作面を確認します。

Safety

Tool annotationsだけに頼らず、権限と承認UIで境界を作ります。

read onlyでも情報流出のリスクは残るため、読める範囲と返す範囲を同時に絞ります。

今回作るものは、GitHub REST APIを何でも代理実行するMCP Serverではありません。Toolはlist_repository_issuesだけに絞り、入力もownerrepostatelabelsper_pageに限定します。返す値もIssue番号、タイトル、状態、ラベル、作者、更新日時、URL、短い本文抜粋までにします。

この狭さが大事です。AIエージェントに「GitHubを調べて」と任せたい時、人間側はつい汎用HTTP Toolや任意GraphQL queryを渡したくなります。しかし最初の接続で任意URL、任意method、任意queryを許すと、実装は短くてもレビュー対象は急に広がります。MCP Serverのレビューでは、コード行数よりも「AIが到達できる操作面」が問題になります。

read-onlyにしても残るリスク

Issueの読み取りは副作用が小さい一方で、情報流出のリスクは残ります。private repositoryのIssueには、顧客名、未公開仕様、内部URL、障害調査ログ、脆弱性メモ、認証まわりの議論が入ることがあります。AIに読ませる以上、writeしないから安全、ではなく、読む情報の範囲を管理する必要があります。

確認項目

MCP Tools仕様では、Tool annotationsを安全性の唯一の根拠にしないことも示されています。Toolの説明にread-onlyと書くことは有用ですが、それは補助情報です。実際の境界は、GitHub tokenの権限、repository allowlist、Tool入力schema、返却フィールド、Host側の承認UIで作ります。

この記事の最小ゴール

読後に確認できる状態は次の3つです。

判断することこの記事での最小解
どのGitHub権限から始めるかpublic repositoryはtokenなし検証、privateや実務検証はfine-grained PATのIssues readから始める
MCP Toolに何を入力させるかrepository、状態、ラベル、件数だけに絞り、任意endpointを受けない
失敗時にどこを見るかMCP schema/transportの問題とGitHub APIの401/403/404/rate limitを分ける

作るものをTool名、入力、出力で固定する

VisualTool contractで固定する項目AI Hostから見える名前、入力、出力を先に決めます。
項目内容見方
Tool名list_repository_issuesとして、Issueを読むToolだと判断できる説明を付けます。
stateopen、closed、allに限定し、defaultはopenにします。
labelsカンマ区切りのlabel条件にし、自由文検索にはしません。
per_page1から30に制限し、大量取得を最小版に入れません。
allowlist許可外repositoryはGitHubへアクセスする前に止めます。
返却フィールドIssue本文全量ではなく、短い抜粋までに留めます。

GitHub側の権限とMCP Server側のallowlistを重ねると、レビューしやすくなります。

Tool名はlist_repository_issuesにします。名前にread-onlyは入れていませんが、descriptionでは作成や更新をしないことを明記します。AI Hostの画面でTool一覧を見た時、人間が「このToolはIssueを読むものだ」と判断できる名前と説明にします。

入力schemaは広げすぎない

最小入力は次の形です。

入力役割最初の制約
ownerGitHub organizationまたはuser文字列、空は禁止
reporepository名文字列、空は禁止
stateopenclosedalldefaultはopen
labelsカンマ区切りのlabel条件任意、自由文検索にはしない
per_page取得件数1から30に制限

per_pageを30にしているのは、最小版で大量取得を避けるためです。GitHub APIの上限だけに合わせるのではなく、AIに渡す文脈量、rate limit、ログの読みやすさを含めて決めます。ページングは次段階で足せます。最初から全Issueを読ませるToolにしない方が、失敗した時の原因も追いやすくなります。

repository allowlistを入れる

Tool入力でownerrepoを受ける場合でも、チーム運用ではallowlistを入れます。たとえば環境変数GITHUB_ALLOWED_REPOS=owner1/repo1,owner2/repo2を読み、許可外のrepositoryはGitHubへアクセスする前に止めます。

allowlistを省くと、tokenが読めるrepositoryの範囲がそのままAIの探索範囲になります。fine-grained PATで対象repositoryを絞っていても、MCP Server側でも同じ境界を表現しておくとレビューしやすくなります。権限はGitHubだけでなく、MCP Serverの入力でも二重に狭めます。

出力はAIが判断に使う分だけ返す

GitHub REST APIのレスポンスをそのまま返す必要はありません。Issue一覧をAIに読ませる目的なら、最初は次のフィールドで十分です。

  • number
  • title
  • state
  • labels
  • author
  • updated_at
  • url
  • body_excerpt

本文全文、全コメント、添付情報、reaction、assignee詳細を最初から返すと、機密情報とトークン消費が増えます。必要なら別Toolとしてget_issue_detailを作り、人間が明示的に呼び出しを承認できる設計に分ける方が扱いやすいです。

GitHub権限はtokenなし、fine-grained PAT、GitHub Appで比べる

VisualGitHub認証方式の使い分け用途、最小権限、運用上の注意を分けて見ます。
項目内容見方
tokenなしpublic repositoryでAPI呼び出しだけ確認できますが、rate limitが低くprivate repositoryは読めません。
fine grained PAT個人または小規模なローカル検証で、対象repositoryのIssues readから始めます。
GitHub Appチーム運用、組織repository、監査を考える時に候補になります。
付けない権限Issues一覧しか読まないToolに、Contents readやPull requests writeを足す理由はありません。
失効と保管PATは有効期限、退職、共有禁止、secret managerでの扱いを決めます。

方式の勝敗ではなく、公開範囲、運用人数、監査要件で選びます。

GitHub連携では、コードを書く前にtoken設計を決めます。最小実装の動作確認だけならpublic repositoryをtokenなしで読む方法があります。ただし、これは実務導入の権限設計ではありません。private repository、組織運用、rate limit、監査を考えると、tokenなし検証で終わらせない方がよいです。

方式向く用途最小権限注意点
tokenなしpublic repositoryでAPI呼び出しだけ確認なしrate limitが低く、private repositoryは読めない
fine-grained PAT個人または小規模なローカル検証対象repositoryのIssues read個人tokenに依存する。失効、退職、共有禁止の設計が必要
GitHub Appチーム運用、組織repository、監査installationごとのrepository権限実装量は増える。この記事では設計比較まで

GitHubのIssues endpointドキュメントでは、fine-grained tokenを使う場合にIssues repository permissionsのreadが必要とされています。この権限でIssue一覧を読めるからといって、Contents readやPull requests writeを一緒に付ける理由にはなりません。ToolがIssues一覧しか読まないなら、GitHub側の権限もそこに合わせます。

fine-grained PATで始める時の決め方

ローカル検証では、fine-grained PATを次の条件で作るのが扱いやすいです。

  • Resource ownerを対象のuserまたはorganizationにする
  • Repository accessを対象repositoryだけにする
  • Repository permissionsはIssues readを中心にする
  • 有効期限を短めにする
  • token値は.envやshellの環境変数に入れ、記事、ログ、スクリーンショットには出さない

注意点

classic PATを使えば簡単に動く場面はあります。ただ、最初の検証で権限を広げると、動いた理由が「コードが正しいから」なのか「tokenが強すぎるから」なのか分からなくなります。うまく動かない時こそ、権限を足す前にendpoint、repository、token対象、organization policyを確認します。

GitHub Appへ進む目安

チームで常用するなら、個人PATを共有する設計は避けます。GitHub Appならrepository単位でinstallでき、個人の退職やtokenローテーションに引きずられにくくなります。rate limitもGitHub Appの扱いになり、組織の規模やEnterprise Cloud条件で変わる場合があります。

ただし、この記事の最小実装ではGitHub Appの認証フローまでは作りません。read-only Toolの入力、出力、失敗条件が固まってから、チーム運用の認証方式へ進む方が、設計の責任分界を保てます。

TypeScriptで最小MCP Serverを組む

Visual最小実装の組み立て順SDK、Zod、GitHub API呼び出し、起動確認を小さくつなげます。
  1. 1依存関係

    @modelcontextprotocol/sdk、zod、typescript、@types/nodeを入れます。

  2. 2tsconfig

    NodeNextでTypeScript実行環境を揃えます。

  3. 3server.ts

    McpServer、stdio transport、Tool登録、GitHub呼び出しを置きます。

  4. 4env

    GITHUB_TOKENとGITHUB_ALLOWED_REPOSを読み、tokenはログへ出しません。

  5. 5確認

    tsc –noEmitとpublic repositoryのIssues API呼び出しを確認します。

最小版ではページングやキャッシュより、権限境界と失敗時の見え方を優先します。

ここからは実装です。検証日は2026年6月15日、Node.js系の一時プロジェクトで@modelcontextprotocol/sdk@1.29.0zodtypescript@types/nodeを入れ、tsc --noEmitが通ることを確認しました。GitHub APIのpublic repository呼び出しも、tokenなしでstatus=200を確認しています。

セットアップ

最小プロジェクトでは、SDKとZodを入れます。TypeScriptで動かすならtsxを使うとローカル確認が楽です。

mkdir github-readonly-issues-mcp
cd github-readonly-issues-mcp
npm init -y
npm install @modelcontextprotocol/sdk@1.29.0 zod
npm install -D typescript tsx @types/node
npm pkg set type=module

tsconfig.jsonはNodeNextにします。

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "types": ["node"]
  },
  "include": ["src/**/*.ts"]
}

Server skeletonとGitHub呼び出し

src/server.tsを作ります。tokenはGITHUB_TOKEN、allowlistはGITHUB_ALLOWED_REPOSから読みます。公開repositoryだけで試すならtokenなしでも呼べる場合がありますが、private repositoryや組織検証ではfine-grained PATなどを使います。

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';

const server = new McpServer({
  name: 'github-readonly-issues',
  version: '0.1.0',
});

const allowedRepos = new Set(
  (process.env.GITHUB_ALLOWED_REPOS ?? '')
    .split(',')
    .map((value) => value.trim())
    .filter(Boolean),
);

const githubToken = process.env.GITHUB_TOKEN;

function assertAllowed(owner: string, repo: string) {
  const key = `${owner}/${repo}`;
  if (allowedRepos.size > 0 && !allowedRepos.has(key)) {
    throw new Error(`Repository is not allowed: ${key}`);
  }
}

server.registerTool(
  'list_repository_issues',
  {
    title: 'List repository issues',
    description:
      'Read GitHub repository issues without creating or updating anything.',
    inputSchema: {
      owner: z.string().min(1),
      repo: z.string().min(1),
      state: z.enum(['open', 'closed', 'all']).default('open'),
      labels: z.string().optional(),
      per_page: z.number().int().min(1).max(30).default(10),
    },
  },
  async ({ owner, repo, state, labels, per_page }) => {
    assertAllowed(owner, repo);

    const url = new URL(
      `https://api.github.com/repos/${owner}/${repo}/issues`,
    );
    url.searchParams.set('state', state);
    url.searchParams.set('per_page', String(per_page));
    if (labels) url.searchParams.set('labels', labels);

    const headers: Record<string, string> = {
      Accept: 'application/vnd.github+json',
      'X-GitHub-Api-Version': '2022-11-28',
      'User-Agent': 'github-readonly-issues-mcp',
    };

    if (githubToken) {
      headers.Authorization = `Bearer ${githubToken}`;
    }

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

    if (!response.ok) {
      const remaining = response.headers.get('x-ratelimit-remaining');
      const reset = response.headers.get('x-ratelimit-reset');
      throw new Error(
        `GitHub API failed: status=${response.status}, ` +
          `remaining=${remaining ?? 'unknown'}, reset=${reset ?? 'unknown'}`,
      );
    }

    const issues = (await response.json()) as Array<any>;
    const output = issues.map((issue) => ({
      number: issue.number,
      title: issue.title,
      state: issue.state,
      labels: Array.isArray(issue.labels)
        ? issue.labels.map((label: any) => label.name).filter(Boolean)
        : [],
      author: issue.user?.login,
      updated_at: issue.updated_at,
      url: issue.html_url,
      body_excerpt:
        typeof issue.body === 'string' ? issue.body.slice(0, 240) : '',
    }));

    return {
      content: [{ type: 'text', text: JSON.stringify(output, null, 2) }],
      structuredContent: { issues: output },
    };
  },
);

const transport = new StdioServerTransport();
await server.connect(transport);

実装時の確認項目

このコードは、GitHub tokenをログへ出さないこと、許可外repositoryをGitHubへ問い合わせる前に止めること、per_pageを最大30にすることを重視しています。最小版なのでページングやキャッシュは入れていません。実務では、同じIssue一覧を何度も読むHost設定になっていないか、rate limitを見ながら足します。

起動と接続確認

package scriptを追加します。

npm pkg set scripts.dev="tsx src/server.ts"
npm pkg set scripts.typecheck="tsc --noEmit"
npm run typecheck

public repositoryでtokenなし検証をするなら、allowlistだけ設定して起動します。

GITHUB_ALLOWED_REPOS=modelcontextprotocol/typescript-sdk npm run dev

private repositoryを読む場合は、fine-grained PATをGITHUB_TOKENに入れます。ここでtokenをshell履歴やログに残さない運用にしてください。記事、Issue、PR、スクリーンショットにtoken値を貼らないのは当然として、CIログやMCP Serverのstderrにも出さないようにします。

export GITHUB_ALLOWED_REPOS=your-org/your-repo
export GITHUB_TOKEN=github_pat_xxx_dummy_value
npm run dev

MCP Inspectorや利用中のAI Hostからstdio serverとして接続し、list_repository_issuesを呼びます。Inspectorで試す場合も、proxy tokenやローカル接続権限が別に存在します。Inspector側の権限設計は、公開済みのMCP Inspectorで自作Serverを検証する前にで整理しています。

権限と失敗条件を先に切り分ける

Visualよくある失敗と見る場所MCP側の問題とGitHub側の問題を混ぜずに確認します。
項目内容見方
Toolが一覧に出ないMCP Server起動失敗やtransport設定を見ます。token権限を広げる問題ではありません。
schema error入力型、enum、件数上限、Zod schemaを確認します。
401tokenなし、token形式不正、失効を確認します。
403権限不足、rate limit、organization policyを切り分けます。
404repository不存在だけでなく、private repository権限不足も疑います。
rate limitx-ratelimit情報を見て、無限リトライを避けます。

エラー解消のために権限を雑に広げず、repository名、対象repository、Issues read、組織ポリシーの順に見ます。

MCP連携の失敗は、MCP側とGitHub側が混ざって見えます。最初に切り分け表を作っておくと、権限不足を雑に解決しなくて済みます。

症状主な原因見る場所やってはいけない解決
Toolが一覧に出ないMCP Server起動失敗、transport設定ミスHost設定、stderr、起動コマンドGitHub token権限を広げる
schema errorになる入力型、enum、件数上限の不一致Tool入力、Zod schema任意入力を許す
401tokenなし、token形式不正、失効GITHUB_TOKEN、GitHub認証 docsclassic PATに広げる
403権限不足、rate limit、organization policyresponse status、rate limit header全repository権限を付ける
404repository不存在、private repository権限不足owner/repo、token対象repositorypublic/privateの切り分けなしに権限追加
429相当やrate limit短時間の過剰呼び出しx-ratelimit-*、Hostの再試行無限リトライ

404を単なる存在確認エラーにしない

GitHubでは、private repositoryへの権限不足が404に見えることがあります。AI Hostからは「repositoryが見つからない」としか見えないかもしれませんが、すぐに広い権限を付けるのは危険です。

評価基準

確認順は、repository名、resource owner、fine-grained PATの対象repository、Issues read権限、organization policyです。これで分からなければ、tokenなしでpublic repositoryを読む最小ケースに戻り、MCP Serverそのものが動いているかを切り分けます。

rate limitは品質問題でもある

GitHubのREST API best practicesでは、過剰なpollingを避け、rate limit errorを受けたらretry-afterx-ratelimit-resetに従って停止することが推奨されています。MCP Toolでも同じです。AIが何度も同じToolを呼ぶ可能性があるため、最小版でも件数上限とエラーメッセージは入れておきます。

次に実装を広げるなら、単純なメモリキャッシュ、repositoryごとの取得間隔、ページングの明示的なTool化を検討します。無限リトライや全ページ取得は、read-onlyでも運用品質を落とします。

失敗点

Visual検証で注意した落とし穴実装根拠、read onlyの見え方、public検証の限界を分けます。
SDK docs

main branch READMEではなく、npm latestと対応するserver docsを確認します。

read onlyの印象

読むだけでも、社内の意思決定や事故調査の文脈がAIへ渡ります。

ログとtrace

モデル入力、Hostの会話履歴、共有範囲までリスクに入れます。

public検証

tokenなしで成功しても、private repository検証の成功は意味しません。

rate limit

rate limit、権限、repository対象を混ぜずに確認します。

再現性のある実装記事では、確認日、SDK版、公式情報の対応を残します。

今回の検証で意外に重要だったのは、SDKのmain branch READMEをそのまま実装根拠にしないことでした。GitHubのmainには将来版の説明が入ることがあるため、記事ではnpm latestとして確認した@modelcontextprotocol/sdk@1.29.0と、そのgitHeadに対応するserver docsを見ました。実装記事では、公式リポジトリの最新branchよりも、読者がインストールするnpm版に合わせる方が再現性があります。

もう1つの失敗点は、read-onlyという言葉が安全に見えすぎることです。GitHub Issuesは「読むだけ」でも、社内の意思決定や事故調査の文脈を含みます。AIに渡す時点で、モデル入力、ログ、trace、Hostの会話履歴、共有範囲までリスクに入ります。Toolの副作用だけでなく、読ませるデータの範囲をレビュー対象にしてください。

最後に、public repositoryでtokenなし検証が通ることは、private repository検証の成功を意味しません。今回のpublic API確認ではx-ratelimit-limit=60が返りました。認証済みrequestではGitHubの説明する個人rate limitに変わりますが、そのtokenがどのrepositoryを読めるかは別問題です。rate limit、権限、repository対象を混ぜないのが大事です。

実務で使うなら

Visualread only版から広げる順番参照範囲を小さく始め、write操作は承認と監査を足してから扱います。
  1. Issue一覧

    state、labels、per_pageで絞れるread only一覧から始めます。

  2. Issue詳細

    単一Issueだけを詳しく読むToolを追加します。

  3. 定型検索

    検索条件を定型化したread only検索Toolに広げます。

  4. comment draft

    人間承認付きのコメント下書き生成を扱います。

  5. write操作

    コメント投稿、Issue作成、label変更は差分表示と承認ログを前提にします。

チーム運用では、個人PATを長期運用にせず、GitHub App、secret manager、監査ログ、承認UIを設計対象にします。

小規模な個人検証なら、public repositoryのtokenなし検証から始め、次にfine-grained PATで対象repositoryとIssues readだけを付ける流れが現実的です。チームで使うなら、個人PATを長期運用にしないでください。GitHub App、secret manager、監査ログ、repository allowlist、Host側の承認UIを設計対象にします。

read-only版から広げる順番

おすすめの拡張順は次の通りです。

  1. statelabelsper_pageで絞れるread-only一覧
  2. get_issue_detailで単一Issueだけを詳しく読むTool
  3. 検索条件を定型化したread-only検索Tool
  4. 人間承認付きのcomment draft生成
  5. 実際のcomment投稿、Issue作成、label変更

境界線

4以降は、write操作です。Tool呼び出し前に差分表示を出し、人間が承認し、誰がどのAI HostからどのToolを呼んだかを残します。AIエージェントの実行ログ設計は、AIエージェントの実行ログ設計:trace・tool call・承認ログを事故調査に使える形で残すも参考になります。

チームルールに入れる項目

AGENTS.mdや開発者向け運用ルールには、次を入れておくとレビューしやすくなります。

  • MCP Serverごとの目的とowner
  • 読み取り対象repositoryのallowlist
  • token種別と失効ルール
  • Toolごとのread/write分類
  • write Toolの人間承認条件
  • tokenやIssue本文をログに出さないルール
  • rate limit時の停止条件
  • SDK、MCP仕様、GitHub API docsの更新確認日

AIコーディングエージェント全体の権限設計は、法人導入前のAIコーディング権限設計で扱っています。この記事のMCP Serverも、その中のGitHub read-only接続として位置づけると、チーム導入時に説明しやすくなります。

セキュリティ・コスト注意

Visualread only MCP Serverで見落としやすい負担破壊的操作ではなく、情報の広がりとトークン消費に注意します。
情報の広がり

短い抜粋でも、会話履歴、trace、共有機能、ログ保存に残る可能性があります。

返却量

必要以上の本文やコメントを返さず、タイトル、状態、ラベル、URL、短い抜粋に絞ります。

token

MCP Server側だけに置き、レスポンスやエラーへ含めません。

HTTP error

status、rate limit remaining、reset時刻程度に留めます。

AI側コスト

Issue本文やコメントを大量に渡すと、入力コストと回答時間が増えます。

MCP Toolは便利なコンテキスト入口ですが、取得量を小さく設計する方が長く使えます。

read-only MCP Serverの主なリスクは、破壊的操作ではなく情報の広がりです。Issue本文の抜粋を返すだけでも、AI Hostの会話履歴、trace、共有機能、ログ保存に残る可能性があります。必要以上の本文やコメントを返さず、最初はタイトル、状態、ラベル、URL、短い抜粋までに絞ります。

tokenはMCP Server側だけに置き、Toolのレスポンスやエラーに含めません。HTTP error時も、status、rate limit remaining、reset時刻程度に留めます。GitHubから返ったエラー本文をそのままAIへ返すと、内部情報や設定断片が混ざる可能性があります。

コスト面では、GitHub APIの料金よりも、AI側のトークン消費と再試行が効きます。Issue本文やコメントを大量に渡すと、LLMの入力コストが増え、回答も遅くなります。MCP Toolは便利なコンテキスト入口ですが、RAGや検索のように、取得量を小さく設計する方が長く使えます。

本サイトではMCP、AI coding agent、権限設計の更新を継続的に検証しています。仕様変更や実装例の更新を追いたい場合は、ニュースレターで新しい検証記事を確認できます。組織のprivate repositoryやMCP設計レビューで迷う場合は、お問い合わせから相談してください。

次に読むなら

参照した主な情報源

  • https://raw.githubusercontent.com/modelcontextprotocol/typescript-sdk/e12cbd7078db388152f6e839abdbe09ba01f3f32/README.md
  • https://raw.githubusercontent.com/modelcontextprotocol/typescript-sdk/e12cbd7078db388152f6e839abdbe09ba01f3f32/docs/server.md
  • https://modelcontextprotocol.io/specification/2025-11-25/server/tools
  • https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices
  • https://modelcontextprotocol.io/docs/concepts/transports
  • https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28
  • https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api?apiVersion=2022-11-28
  • https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28
  • https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api?apiVersion=2022-11-28
  • https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens

更新履歴

Visualこの記事の確認履歴初版時に確認したSDK、仕様、GitHub API、検証結果をまとめます。
  1. 2026-06-15

    @modelcontextprotocol/sdk 1.29.0、MCP 2025-11-25 Tools仕様、Security Best Practicesを確認しました。

  2. 2026-06-15

    GitHub REST Issues API、fine grained personal access token、rate limitsを確認しました。

  3. 2026-06-15

    TypeScript最小Serverのtsc –noEmitと、public repositoryのIssues APIをtokenなしで確認しました。

SDKやGitHub APIの仕様は変わるため、実装時は現在の公式情報も確認します。

  • 2026-06-15: 初版。@modelcontextprotocol/sdk@1.29.0、MCP 2025-11-25 Tools仕様、MCP Security Best Practices、GitHub REST Issues API、fine-grained personal access token、rate limitsを確認。TypeScript最小Serverのtsc --noEmitが通ること、public repositoryのIssues APIをtokenなしでstatus=200取得できることを確認。