github-mcp
by nht-x
README.md
# knowledge-mcp
社内ナレッジをGitHubの保管庫に貯め、目次から辿って読むためのMCPサーバー。
ChatGPT や Claude などのMCP対応クライアントから、社内の記録を検索・保存できます。
## できること
| ツール | 種別 | 内容 |
|---|---|---|
| `get_knowledge_root` | 読み取り | 目次の入口を読む |
| `get_knowledge_index` | 読み取り | 分類ごとの一覧を読む |
| `get_knowledge_record` | 読み取り | 記録を1件読む |
| `save_knowledge` | 書き込み | 記録を1件保存する |
**この4つだけです。** ファイル削除・Issue作成・リポジトリ操作などの権限は持ちません。
## 探し方の設計
いきなり全文検索せず、**目次から辿ります**。
```
get_knowledge_root 目次の入口(ROOT.md)
↓ 分類を選ぶ
get_knowledge_index 分類ごとの一覧(knowledge/{分類}/INDEX.md)
↓ 記録を選ぶ
get_knowledge_record 記録の本文
```
目次は記録の位置を `path: knowledge/policy/abc.md` の形で載せています。
`get_knowledge_record` にはこのうち**最後の「/」より後ろだけ**を渡してください
(分類は `category` へ分けて渡します)。
```
目次の表記 path: `knowledge/policy/20260805T060019Z-abc.md`
└─ category ─┘ └────── filename ──────┘
```
ディレクトリを含む文字列は拒否されます。`.github/workflows/` へ書き込めると
任意のコードが実行できてしまうため、パス区切りは受け付けない設計です。
取り違えたときは、エラーメッセージが渡すべき形を示します。
分類は5つです。
| 分類 | ディレクトリ | 内容 |
|---|---|---|
| Policy | `policy` | 社内ルール・方針 |
| How-to | `how-to` | 作業手順・やり方 |
| Q&A | `qa` | よくある質問と答え |
| Reference | `reference` | 参照表・データ |
| Decision | `decision` | 決定事項とその理由 |
## 保存のしくみ
ファイルを直接コミットせず、**保管庫のワークフローを起動します**。
```
save_knowledge
→ POST /repos/{owner}/{repo}/dispatches
(event_type: save_knowledge・日本語のまま送る)
↓
保管庫の GitHub Actions が起動
↓
形式変換 → コミット → 目次の再生成 → 個人情報チェック
```
**AIやサーバー側でBase64変換を行いません。** 変換はワークフロー側の責務です。
保存は非同期のため、ツールの成功は「受け付けた」ことを意味します。入力に不備があった場合は、保管庫に「保存失敗」として記録されます。
## セットアップ
### 1. 依存のインストール
```bash
poetry install
```
### 2. 環境変数
`.env.example` をコピーして `.env` を作成します。
```bash
cp .env.example .env
```
| 変数 | 必須 | 内容 |
|---|---|---|
| `KNOWLEDGE_REPOSITORY` | ✅ | 保管庫のリポジトリ(`owner/repo`) |
| `GITHUB_TOKEN` | ✅ | アクセストークン |
| `KNOWLEDGE_SAVE_RATE_LIMIT_PER_MINUTE` | | 1分あたりの保存上限(既定 10) |
| `MCP_TRANSPORT` | | `stdio`(既定)または `streamable-http` |
### 3. トークンの権限
**Fine-grained PAT を推奨します。** 権限は必要最小限にしてください。
```
対象リポジトリ : 保管庫の1つだけ
権限 : Contents — Read and write
付与しないもの : Actions / Issues / Administration / Workflows
```
### 4. 起動
```bash
# stdio(ローカルのMCPクライアント向け)
poetry run knowledge-mcp
# Streamable HTTP(リモート接続向け)
MCP_TRANSPORT=streamable-http poetry run knowledge-mcp
```
## セキュリティ
AIに書き込み権限を渡すため、**AIの判断に依存しない防御**を実装しています。
| 対策 | 内容 |
|---|---|
| **パス検証** | 分類はホワイトリスト方式。ファイル名は `.md` のみ許可し、`..` `/` `\` と隠しファイルを拒否 |
| **最小の権限** | ツールは4つのみ。削除・Issue作成・ワークフロー実行の手段を持たない |
| **レート制限** | 1分あたりの保存回数に上限。プロンプトインジェクションによる大量保存への歯止め |
| **入力の上限** | タイトル120字/概要500字/本文10,000字/タグ10個 |
| **秘密情報** | 暗号化せず環境変数から直接読む(鍵を同じ場所に置く二重管理はしない) |
| **タイムアウト** | 全HTTPリクエストに設定(接続5秒/読み取り30秒) |
| **リダイレクト** | 追わない。意図しない転送先へトークンが送られることを防ぐ |
| **ログ** | 本文・トークンを記録しない。エラーの詳細は標準エラーのみに出す |
| **ログ偽装対策** | エラーメッセージに含める入力値から制御文字を除去し、長さを切り詰める |
### パス検証について
`.github/workflows/` に書き込めると**任意のコードが実行できてしまう**ため、
分類名はホワイトリスト方式で固定しています。相対参照や絶対パスは、
ホワイトリストに存在しない時点で弾かれます。
ファイル名の正規表現には `\A` と `\Z` を使っています。
Python の `$` は「文字列末尾の直前の改行」にもマッチするため、
`^...$` だと `"file.md\n"` のような入力が通ってしまいます。
## ログ
操作の記録と、障害を追うための情報を1行1JSONで出します。
出力先は常に標準エラーです(stdio 通信では標準出力が MCP プロトコル専用のため)。
### 何が残るか
| 種類 | レベル | 内容 | 本番(INFO)で残るか |
|---|---|---|---|
| `tool.started` | DEBUG | 呼び出しの開始 | ✗ |
| `tool.completed` | INFO | **操作ログ**。何が実行されたか | ✓ |
| `tool.denied` | WARNING | **弾いた入力**。検証エラー・レート制限 | ✓ |
| `tool.failed` | ERROR | 失敗。原因の種別つき | ✓ |
```json
{"timestamp":"2026-09-01T05:12:33.123Z","level":"WARNING","logger":"knowledge_mcp.audit",
"message":"tool.denied",
"extra":{"tool":"get_knowledge_index","request_id":"71346ae6020f",
"category":"../.github/workflows","outcome":"denied",
"reason":"validation_error","duration_ms":0.1}}
```
### 調べ方
1回の呼び出しに `request_id` が振られ、その間に出た全てのログに同じ値が付きます。
```bash
grep '71346ae6020f' server.log
```
```bash
jq 'select(.extra.tool=="save_knowledge")' server.log
jq 'select(.message=="tool.denied")' server.log
jq 'select(.extra.duration_ms > 3000)' server.log
```
**`tool.denied` が増えていたら中身を見てください。** 検証で弾いた記録であり、
AIが仕様を取り違えているか、指示文が誘導に乗せられている可能性があります。
上の例では、保管庫の外(`.github/workflows`)を読もうとしたことが残っています。
### 保存の追跡
保存は非同期です。このサーバーのログに残るのは**受け付けたところまで**で、
実際にコミットされたかは保管庫側の Actions の記録を見ます。
突き合わせられるよう、保存時の `request_id` を控えておいてください。
### 残さないもの
記録の本文・タイトル・トークンは出しません。本文は長さ(`body_chars`)だけを残します。
何が保存されたかは保管庫の履歴で確認してください。ログに業務内容を写すと、
ログの保管場所にも同じ機密性が必要になるためです。
トークンは形式によらず伏せられます(`GITHUB_TOKEN=` / `GITHUB_PAT=` のような
環境変数名の形も含む)。
## 他のMCPサーバーとの統合
`register(server)` を公開しているため、1台のサーバーに複数のMCPをまとめられます。
```python
from mcp.server import MCPServer
from chatwork_mcp.interfaces.mcp_tools import register as register_chatwork
from knowledge_mcp.interfaces.mcp_tools import register as register_knowledge
server = MCPServer("integration", version="1.0.0")
register_chatwork(server)
register_knowledge(server)
server.run(transport="stdio")
```
クライアントからは1つのアプリとして見え、サーバーの費用も1台分で済みます。
## 構成
```
src/knowledge_mcp/
├── domain/ 外部に依存しない。保管庫の構造と検証ルール
│ ├── models.py
│ └── validators.py
├── application/ ユースケース
│ └── usecases.py
├── infrastructure/ 外部との接続
│ ├── config.py
│ ├── github_client.py
│ └── rate_limiter.py
├── interfaces/ MCPとの接続
│ └── mcp_tools.py
├── observability/ 運用のための記録
│ ├── audit.py 操作の記録と相関ID
│ └── log_config.py 出力形式・秘密情報の伏せ字・保存先
└── mcp_server.py エントリポイント
```
依存の向きは常に**外側から内側**です。MCP SDK の仕様が変わっても
`domain` と `application` は影響を受けません。
## テスト
```bash
poetry run pytest
```
## ライセンス
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues