社外のコードは書けるのに社内のコンテキストを何も知らない──Claude Codeの最大の弱点を、MCPサーバー3つで埋めた1ヶ月の記録。
結論:MCPサーバー3つで「前提説明プロンプト」が72%減った
Claude Codeは優秀です。コードの生成・リファクタリング・テスト作成、どれも高い精度で応えてくれます。
しかし、社内のことを何も知りません。
- 「うちのユーザーテーブルのカラム定義はこうで…」
- 「この設計判断はADRの#023に書いてあるんだけど…」
- 「先月Slackで議論した結果、この方針になったんだけど…」
毎回これを説明するプロンプトを書いていました。チームメンバー5人が、それぞれ独自の「前提説明テンプレ」を持っている状態。これは明らかにおかしい。
そこで MCP(Model Context Protocol)サーバーを3つ自作 し、Claude Codeから社内のNotion・Slack・PostgreSQLに直接アクセスできるようにしました。結果、前提説明プロンプトの文字数が平均72%削減。Claude Codeが「社内事情を知るエージェント」に変わりました。
環境・前提条件
| 項目 | バージョン・サービス |
|---|---|
| Claude Code | 最新版(2025年6月時点) |
| MCP SDK |
@modelcontextprotocol/sdk 1.x |
| Node.js | v20 LTS |
| Notion API | 2022-06-28 |
| Slack API | Web API + Events API |
| DB | PostgreSQL 16 |
| 実行環境 | macOS(ローカル開発)/ Docker(チーム共有) |
MCPの基本概念についてはAnthropicの公式ドキュメント(後述の参考リンク)を前提とします。
全体アーキテクチャ
まず完成形の構成を示します。
ポイントは3つです。
- 各MCPサーバーは独立したプロセスとして動作し、Claude Codeの設定ファイルで束ねる
- セキュリティレイヤーをMCPサーバー内部に実装する(外部APIとClaude Codeの間にフィルタを挟む)
- 各サーバーはstdio通信で接続し、ローカルで完結する
Step1:Notion MCPサーバーの構築
目的
設計ドキュメント(ADR・API仕様・ER図の説明文)をClaude Codeから参照可能にします。
実装のポイント
Notion MCPサーバーでは、以下の3つのツールを公開しました。
// tools の定義(抜粋)
const tools = [
{
name: "search_design_docs",
description: "設計ドキュメントをキーワード検索する",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "検索キーワード" },
doc_type: {
type: "string",
enum: ["adr", "api_spec", "architecture"],
description: "ドキュメントの種別"
}
},
required: ["query"]
}
},
{
name: "get_page_content",
description: "指定IDのNotionページの内容を取得する",
inputSchema: {
type: "object",
properties: {
page_id: { type: "string" }
},
required: ["page_id"]
}
},
{
name: "list_recent_adrs",
description: "直近N件のADR(Architecture Decision Record)一覧を返す",
inputSchema: {
type: "object",
properties: {
limit: { type: "number", default: 10 }
}
}
}
];
claude_desktop_config.json への登録
{
"mcpServers": {
"notion-docs": {
"command": "node",
"args": ["./mcp-servers/notion/dist/index.js"],
"env": {
"NOTION_API_KEY": "secret_xxx",
"NOTION_ROOT_PAGE_ID": "xxx"
}
}
}
}
これだけでClaude Codeに /search_design_docs と打つ必要すらなく、「うちのユーザー認証の設計方針を教えて」と聞くだけで、ADRを検索して回答してくれるようになりました。
Step2:Slack MCPサーバーの構築
目的
過去の技術議論スレッドを検索し、「なぜこの実装になったのか」という経緯をClaude Codeに注入します。
実装のポイント
const tools = [
{
name: "search_tech_threads",
description: "技術チャンネルの過去スレッドを検索する",
inputSchema: {
type: "object",
properties: {
query: { type: "string" },
channel_names: {
type: "array",
items: { type: "string" },
description: "検索対象チャンネル名(例: ['dev-backend', 'dev-infra'])"
},
days_back: { type: "number", default: 90 }
},
required: ["query"]
}
}
];
検索対象チャンネルをホワイトリスト制にしているのが重要です。#general や #random、HR関連チャンネルは絶対に含めません。これについては後述の「想定外だったこと②」で詳しく書きます。
Step3:社内DB MCPサーバーの構築
目的
テーブル定義(スキーマ)とサンプルデータをClaude Codeから参照し、SQLやマイグレーションの生成精度を上げます。
権限設計が最も重要
具体的には以下のルールを設けました。
| ルール | 内容 |
|---|---|
| 接続ユーザー |
mcp_readonly (SELECT権限のみ) |
| データ取得上限 | 1テーブルあたり最大5行 |
| 個人情報マスキング | email, phone, name カラムは *** に置換 |
| 除外テーブル |
audit_logs, payment_*, user_secrets
|
| クエリ制限 | 任意SQLの実行は不可。事前定義したツールのみ |
// サンプルデータ取得時のマスキング処理(抜粋)
const MASKED_COLUMNS = ["email", "phone", "name", "first_name", "last_name"];
function maskRow(row: Record<string, unknown>): Record<string, unknown> {
const masked = { ...row };
for (const col of MASKED_COLUMNS) {
if (col in masked) {
masked[col] = "***MASKED***";
}
}
return masked;
}
任意SQLを実行させないことが最大のポイントです。MCPサーバーが提供するのは get_table_schema、get_sample_data、list_tables の3ツールだけ。Claude Codeが「このSQLを実行して」と言っても、MCPサーバー側で弾きます。
想定外だったこと①:Notionのトークン圧迫問題
何が起きたか
Notionのページにはネストされたブロックがあり、1ページ取得するだけで数千〜数万トークンになることがありました。設計ドキュメントは図やテーブルが多く、特に肥大化しがちです。
Claude Codeのコンテキストウィンドウを圧迫し、肝心のコード生成に使えるトークンが減る本末転倒な状態に。
解決策:要約レイヤーを挟む
実装としては以下のアプローチを取りました。
- セクション見出し抽出:H1〜H3の見出しだけを抜き出した「目次」を先に返す
- セクション単位取得:Claude Codeが必要なセクションだけを追加取得できるツールを用意
- キャッシュ:同じページへのリクエストは1時間キャッシュ
// ページ内容を構造化して返す
function structurePage(blocks: NotionBlock[]): StructuredPage {
const sections = extractSections(blocks); // H1-H3で分割
const toc = sections.map(s => ({
id: s.id,
heading: s.heading,
level: s.level,
estimatedTokens: estimateTokens(s.content)
}));
return {
toc,
totalEstimatedTokens: toc.reduce((sum, s) => sum + s.estimatedTokens, 0),
sections // 個別取得用
};
}
これにより、1回のリクエストで消費するトークンが平均85%削減できました。
想定外だったこと②:Slack MCPで機密情報が漏れかけた
何が起きたか
テスト中、Claude Codeに「認証周りの過去の議論を探して」と依頼したところ、Slackの検索結果に本番環境のAPIキーが平文で貼られたスレッドが含まれていました。
MCPサーバーがそのままレスポンスを返していたため、Claude CodeのコンテキストにAPIキーが入ってしまいました。
解決策:多層フィルタリング
最終的に以下の3層でフィルタリングする設計にしました。
| 層 | フィルタ内容 | 実装場所 |
|---|---|---|
| 第1層 | チャンネルホワイトリスト | MCPサーバー起動時設定 |
| 第2層 | 正規表現による秘匿情報検出 | レスポンス整形時 |
| 第3層 | 特定キーワードを含むメッセージの除外 | レスポンス整形時 |
// 第2層:秘匿情報の検出と置換
const SECRET_PATTERNS = [
/(?:api[_-]?key|secret|token|password)\s*[:=]\s*\S+/gi,
/(?:sk-|pk_|rk_)[a-zA-Z0-9]{20,}/g, // APIキー形式
/(?:ghp_|github_pat_)[a-zA-Z0-9]{36,}/g, // GitHubトークン
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, // メールアドレス
/xox[baprs]-[a-zA-Z0-9-]+/g, // Slackトークン
];
function sanitizeMessage(text: string): string {
let sanitized = text;
for (const pattern of SECRET_PATTERNS) {
sanitized = sanitized.replace(pattern, "[REDACTED]");
}
return sanitized;
}
このフィルタリング設計はNotion MCPサーバーにも横展開しました。Notionにもたまに秘匿情報が書かれていることがあるためです。
効果測定:導入前後の比較
導入から3週間後、チームメンバー5人の利用データを集計しました。
| 指標 | 導入前 | 導入後 | 変化 |
|---|---|---|---|
| 前提説明プロンプトの平均文字数 | 1,847文字 | 512文字 | -72.3% |
| Claude Codeへの1タスクあたり平均ターン数 | 6.2回 | 3.8回 | -38.7% |
| 「スキーマ教えて」系の質問回数/日 | 12.4回 | 1.1回 | -91.1% |
| MCPツール呼び出し回数/日(チーム計) | - | 47.3回 | - |
特に「スキーマ教えて」系の質問がほぼゼロになったのは大きいです。Claude Codeが自分でDB MCPサーバーに問い合わせて、テーブル定義を取得してからコードを書いてくれます。
体感としても「Claude Codeが同じチームのメンバーになった」感覚です。「うちの orders テーブルに status カラムを追加するマイグレーション書いて」と言えば、既存のスキーマを確認し、ADRの命名規則を参照し、適切なマイグレーションファイルを生成してくれます。
MCPは「Claude Codeに記憶を与える」インフラである
MCPサーバーの本質は、APIのラッパーではありません。
組織の暗黙知を、AIが参照可能な形に構造化するインフラです。
これまでClaude Codeは「優秀だけど新入社員」でした。何を聞いてもそれなりの回答をくれるけど、社内の文脈を知らない。毎回オンボーディングが必要。
MCPサーバーを3つ立てたことで、Claude Codeは「社内事情を知っているシニアエンジニア」に近づきました。完璧ではありませんが、少なくとも「うちのDBのカラム名」や「なぜこの設計にしたか」を自分で調べてくれます。
まとめ
- MCPサーバーは「AIに組織の記憶を与える」インフラであり、Notion・Slack・DBの3つを繋ぐだけで前提説明プロンプトが72%減った
- セキュリティ設計は必須。特にSlack連携では秘匿情報フィルタリング、DB連携ではREAD ONLY権限+データマスキングを多層で実装すべき
- トークン圧迫への対策として、Notionのようなリッチコンテンツは「目次→セクション単位取得」の2段階アーキテクチャが有効