はじめに
以前、全社17DB・994テーブルを安全に横断検索・クエリ実行できる「DB Graph MCP」を紹介しました。ありがたいことに反響をいただいたので、今回はDB Graph以外に社内で運用しているMCPサーバー群をまとめて紹介します。
これらは2026年1月から約3ヶ月で構築したもので、現在社内で稼働しているMCPサーバーはDB Graphを含めて17個。DB、インフラ、ドキュメント、プロジェクト管理、オブザーバビリティ、CI/CD、さらには非エンジニアによるコード修正・デプロイまで、社内業務のかなり幅広い領域をAIから操作できる状態になっています。
全体像
まず全サーバーの一覧です。
| カテゴリ | サーバー | 説明 |
|---|---|---|
| データ | DB Graph | 全社DB辞書 + 実DBクエリ |
| インフラ | GCloud | GCPリソースのリードオンリー参照 |
| AWS | AWSリソースのリードオンリー参照 | |
| ドキュメント・ナレッジ | GWS | Google Workspace全サービス操作 |
| Git Server | 全社Gitリポジトリのリードオンリー参照 | |
| グラフ | Code Graph | コードベース解析(関数→API→DB→イベントの依存追跡) |
| Product Graph | code + DB + docs統合ナレッジグラフ | |
| Biz Graph | 施策×指標の関連性グラフ | |
| オブザーバビリティ | Grafana | ログ・メトリクス・アラート参照 |
| CI/CD | CircleCI | パイプライン実行・ビルドログ・テスト結果 |
| プロジェクト管理 | Project Management | BQ/Firestore/Sheets連携のPM支援 |
| 業務特化 | Stylist Insights | スタイリストのパフォーマンス・KPIデータ |
| UX Insights | UX分析用BQ集計データ | |
| freee | freee API連携 | |
| 開発基盤 | Workspace | 社内モノレポのACL付き編集・デプロイ |
| Sandbox | 非エンジニア向けアプリデプロイ |
これらはすべてTypeScriptで実装され、PulumiでGCPにデプロイされ、Google OAuthで認証されています。
設計思想
なぜこんなに分けたのか
1つの巨大なMCPサーバーに全機能を詰め込むこともできますが、あえてサーバーを分けています。理由は以下のとおりです。
- 認証スコープの分離 — GWSサーバーにはWorkspace APIのスコープが必要だが、DBクエリサーバーには不要。スコープを最小限にすることで権限の暴発を防ぐ
- デプロイ独立性 — Grafanaサーバーの変更がDBクエリに影響しない。障害の爆発半径を小さくする
- ユーザーごとの選択 — エンジニアは全部入れるが、マーケチームはGWSだけ、というように必要なものだけ
.mcp.jsonに追加すればいい
共通基盤
全サーバーに共通するパターンがあります。
認証: 共通パッケージでGoogle OAuth 2.0 + PKCEを実装。RFC 8414の自動検出に対応しており、.mcp.json にURLを書くだけでClaude Codeが自動的に認証フローを開始します。ビジネスサイド向けにはClaudeの組織設定でカスタムコネクタとして追加するだけです。
{
"mcpServers": {
"server-name": {
"type": "http",
"url": "https://mcp-xxx.your-domain.example/mcp"
}
}
}
これだけ。auth ブロック不要。全サーバーでこの形式です。
セッション管理: Upstash Redisで全サーバー共通のセッションストアを提供。SSOクッキーにより、1回ログインすれば全サーバーにアクセスできます。
ツール利用ログ: 全ツール呼び出しをBigQueryに記録しています。誰がいつ何のツールを使ったかがすべて追跡可能。利用率やエラー率、使われ方を見て改善サイクルを回しています。
インフラ系: GCloud / AWS
みなさん、クラウド環境の調査をAIにやらせたいと思ったこと、もしくは実際にやったことはありませんか?
そして同時に思ったはずです。「それやらせて大丈夫か?」と。
私の場合、管理者権限まで持っていたりするのでなおさら怖い。というわけで絶対に参照しかできないMCPを作成しました。
こだわりポイントは2つあります。
- OIDC / STS / Impersonateを使ったセキュアな認証 — 永続的なクレデンシャルは一切使わない
- アカウント別に必ずログが残る — GCP Audit Log / CloudTrailに個人のメールアドレスが記録される
GCloud MCP
Claude Code → MCP Server → gcloud CLI subprocess → GCP APIs
gcloud CLIをCloud Run上で実行します。ポイントはOAuthスコープで書き込みを原理的に不可能にしていることです。
- OAuthスコープ:
cloud-platform.read-only - GCP APIはスコープとIAMの両方をチェックするため、ユーザーが管理者権限を持っていても書き込み不可
- GCP Audit Logにユーザーのメールアドレスが記録される
- 退職時はGoogle Workspaceアカウントを無効化するだけで自動失効
# こんなことができる
「prod の Cloud Run サービス一覧を見せて」
「このサービスの環境変数を確認して」
「Secret Manager のシークレット一覧を出して」
AWS MCP
GCloudと同じ思想ですが、AWSはGoogle OAuthを直接受けられないため、間にSTSを挟みます。
Claude Code → MCP Server → GCPメタデータ → ID Token
→ AWS STS AssumeRoleWithWebIdentity → 一時credentials
→ aws CLI subprocess → AWS APIs
2層の安全装置:
- IAMロールに
ReadOnlyAccessポリシーのみアタッチ - 一時クレデンシャルの有効期限(1時間)
マルチAWSアカウントにも対応しています。profile パラメータでアカウントを切り替えられ、CloudTrailには assumed-role/mcp-aws-readonly/user@example.com の形式で記録されます。
ドキュメント・ナレッジ系: GWS / Git Server
GWS (Google Workspace) MCP
Google Workspaceの全サービスをClaude Codeから操作できるサーバーです。
Claude Code → MCP Server → gws CLI subprocess → Google Workspace APIs
gws CLIをリモート実行する構成。ユーザーのOAuthアクセストークンをそのまま渡すので、各自の権限でアクセスします。自分のDriveは見えるが、他人のDriveは見えない、という挙動になります。
OAuth認証と同時にGoogle Workspaceの認可も通るので、MCPに接続した時点で自分のWorkspaceリソースに即座にアクセスできるのが体験として良いところです。追加のログインやトークン設定は一切不要です。
# こんなことができる
「このスプレッドシートの売上データをまとめて」
「先週のカレンダーから会議の議事録を抽出して」
「このドキュメントの内容を要約して」
Git Server MCP
全社Gitリポジトリをリードオンリーで参照するサーバーです。
作った動機はGitHub MCPのレートリミット回避です。GitHubの公式MCPサーバーは内部的にGitHub APIを叩くため、レートリミットに縛られます。AIにコードベースを調査させると驚くほどあっという間に上限に達して使い物にならなくなります。
Git Server MCPはGCE VM上で全リポジトリのmainブランチのクローンを保持しており、ローカルのgitコマンドで操作するためレートリミットが一切ない。いくらでもクエリできます。
| ツール | 説明 |
|---|---|
git_blame |
各行の最終変更コミットを取得 |
git_log |
コミット履歴 |
git_grep |
リポジトリ横断テキスト検索 |
git_show |
コミット詳細 |
git_diff |
コミット間の差分 |
read_file |
ファイル読み取り |
list_files |
ファイル一覧 |
search_repos |
リポジトリ検索 |
GitHubアカウントがなくても、OAuth認証さえ通ればコードを読めます。
オブザーバビリティ: Grafana MCP
公式 mcp/grafana DockerイメージをCloud Runにデプロイし、OAuthプロキシを前段に置く構成です。
Claude Code → OAuth Proxy → mcp-grafana → Grafana Cloud
PromQL / LogQLクエリ、ダッシュボード参照、アラートルール確認などが可能です。
ここで重要なのは、Grafanaのダッシュボードやアラートルール自体もリポジトリ内でPulumi(TypeScript)として定義されていることです。つまり、
- アプリケーションコードを書く
- そのアラートルールも同じリポジトリで定義する
- 本番でアラートが発火する
- Claude CodeがGrafana MCPでログを読む
- 同じリポジトリのコードを修正する
というコード → インフラ → オブザーバビリティ → 調査 → 修正のループが完全に閉じています。
CI/CD: CircleCI MCP
CircleCI API v2と連携するサーバーです。共有CircleCIトークンをGoogle SSO認証の背後に配置し、チーム全員がトークン管理なしで利用可能になっています。
Claude Code → OAuth Proxy → CircleCI MCP (sidecar) → CircleCI API v2
Cloud Runのマルチコンテナ構成で、公式 @circleci/mcp-server-circleci をサイドカーとして動かし、前段にOAuthプロキシを置いています。
# こんなことができる
「mainブランチの最新パイプラインのステータスを教えて」
「このビルドの失敗ログを見せて」
「フレーキーテストを検出して」
プロジェクト管理: Project Management MCP
Firestoreの課題管理操作と、Slack・Meetの会話をセマンティック検索できるサーバーです。
主な機能は以下のとおりです。
- 課題管理: Firestore上のイシューの作成・ステータス更新・一覧取得(スプレッドシートへのデュアルライト付き)
- コンテキスト検索: Meetの会議メモやSlackの会話をベクトル検索 + Gemini要約で横断検索
- プロジェクト概要: 担当プロジェクトのマイルストーン・メンバー・設計書・テストケースの参照
- Backlog連携: チケットの親子関係をBQ経由で取得
業務特化系
Stylist Insights / UX Insights MCP
それぞれスタイリストのパフォーマンス・KPIデータ、UX分析用の集計データにアクセスするサーバーです。BQの集計テーブルに対するクエリインターフェースを提供します。
freee MCP
freee APIにOAuth認証付きで接続するプロキシサーバーです。会計データの参照に使います。
開発基盤: Workspace / Sandbox
ここが一番ユニークなところかもしれません。
Workspace MCP — GitHubアカウント不要のコード編集
社内モノレポのACL付きファイル編集・コミット・PR作成・デプロイを提供するサーバーです。
GitHubアカウントは不要で、Google Workspaceアカウント(OAuth)のみで利用可能です。
1. workspace_init → worktree 作成、ブランチ初期化
2. workspace_write_file → コード編集
3. workspace_diff → 変更確認
4. workspace_commit → コミット
5. workspace_push → GitHub に push
6. workspace_deploy → feature ブランチからデプロイ(テスト)
7. 動作確認 OK
8. workspace_create_pr → レビュー依頼
アクセス制御はFirestoreで管理しています。管理者が各ユーザーに対して編集・デプロイ可能なスタック(ディレクトリ)を設定します。
{
"allowedPaths": ["apps/web/xxx/", "apps/api/xxx/"],
"allowedStacks": ["api-xxx", "pages-xxx"],
"role": "developer"
}
これにより、非エンジニアでも自分に許可されたスタックだけを安全に編集・デプロイできます。実際に、非エンジニアのメンバーがAI + Workspace MCPでフルスクラッチのKPIダッシュボードの改善を行っています。
Sandbox MCP — 非エンジニアのアプリデプロイ
さらに踏み込んで、非エンジニアが自分のアプリを社内公開できるサーバーです。
1. sandbox_init_repo(app_name: "my-tool") → リポジトリ初期化
2. sandbox_write_file(...) → ファイル書き込み
3. sandbox_publish(app_name: "my-tool") → Cloud Run デプロイ
→ https://sbx-{nickname}--my-tool.example.com/
gcloudもDockerも不要です。Claudeに「こういうツールが欲しい」と言うだけで、アプリが社内URLで公開されます。
公開されたアプリはCloudflare AccessでGoogle Workspace認証がかかるため、社内メンバーだけが安全にアクセスできます。インターネットに公開されていても組織外からはアクセス不可能です。
詳細はSandbox MCPの記事にまとめています。
グラフ系: Code Graph / Product Graph / Biz Graph
コードベースやビジネスロジックをグラフ構造で解析するサーバー群です。
| サーバー | 対象 | 特徴 |
|---|---|---|
| DB Graph | 全社DB | テーブル辞書 + セマンティック検索 + 実DBクエリ + PII匿名化 |
| Code Graph | 全社のソースコード(複数リポジトリ横断) | 静的解析で関数→API→DB→イベントの依存をリポジトリをまたいで追跡 |
| Product Graph | 社内モノレポ全体 | code + DB + docsを統合したナレッジグラフ。全ノードにビジネスコンテキスト付き |
| Biz Graph | ビジネス施策と指標 | 施策×指標の関連性グラフ |
それぞれ設計思想が異なり、解決する課題も違います。DB GraphはDB Graph MCPの記事、Biz GraphはBiz Graphの記事で詳しく紹介しています。
セキュリティモデル
全サーバーに共通するセキュリティの考え方を整理します。
多層防御
Layer 1: Google Workspace OAuth + ドメイン制限
→ 組織ドメインのみ。組織外はログイン不可
Layer 2: SSO + セッション管理
→ Upstash Redis、7日TTL、スライディングウィンドウ
Layer 3: サーバーごとのスコープ制限
→ GCloud: cloud-platform.read-only
→ AWS: ReadOnlyAccess ポリシー
→ DB Graph: SELECT のみ + PII匿名化
Layer 4: データレベルの保護
→ PII自動匿名化(40+カラムパターン)
→ 機密データセットは BQ IAM で制御
→ 本番DBはリードレプリカのみ
Layer 5: 監査ログ
→ 全ツール呼び出しを BQ に記録
→ GCP Audit Log / CloudTrail に個人メール記録
退職時の自動失効
全サーバーがGoogle OAuthに依存しているため、Google Workspaceアカウントを無効化するだけで全MCPへのアクセスが自動失効します。個別のトークン失効やアカウント削除は不要です。
まとめ
自社MCPサーバーを開発・運用して得た知見をまとめます。
1. 認証は共通化すべき OAuthの実装を共通パッケージにしたことで、新しいサーバーの追加が圧倒的に楽になりました。認証まわりのコードは各サーバーで10行程度です。
2. リードオンリーから始める GCloud / AWS / Git Serverはすべてリードオンリーです。まず読み取りだけ許可し、本当に必要になったら書き込みを追加する。このアプローチでセキュリティの議論がシンプルになります。
3. 既存ツールをラップする gcloud CLI、aws CLI、gws CLI、CircleCI MCP — 既存のCLIやMCPサーバーをOAuthプロキシの背後に置くだけで、チーム全員が安全に使えるようになります。ゼロから作る必要はありません。
4. 非エンジニアへの開放が一番楽しみ Workspace MCPとSandbox MCPにより、GitHubアカウントを持たないメンバーでもコードを修正してデプロイできる基盤が整いました。まだ作ったばかりで大きな成果はこれからですが、ここが一番ポテンシャルがあると思っています。
5. すべてを同じリポジトリで管理する アプリケーション、インフラ(Pulumi)、オブザーバビリティ(Grafanaアラートルール)、MCPサーバー、すべてが1つのモノレポにあります。これにより「コードを書く → デプロイする → 監視する → 問題を見つける → 修正する」のループが完全に閉じます。
DB Graph MCPの記事で「テーブル間の繋がり方が特定の人の頭の中にのみ存在する」という課題を紹介しました。MCPサーバー群の全体像を見ると、これはDBに限った話ではないことがわかります。
インフラの状態、コードの依存関係、ドキュメントの内容、プロジェクトの進捗、ユーザーの行動ログ — これらすべてが「特定の人の頭の中」にある状態を解消するのが、MCPサーバー群の本質的な役割です。
知識をAIがアクセスできる形で外部化する。それが全MCPサーバーの共通テーマです。
comments (3)
Running 17 MCP servers across internal operations is a significant architectural achievement — most teams struggle to maintain even 3 or 4 reliably. The coordination overhead alone across Slack, GitHub, BigQuery, and custom internal tools must have been substantial to get right. The question that becomes unavoidable at that scale is governance: when 17 agents are running with write access across your internal systems, how do you reconstruct exactly what each one did when something unexpected happens? How do you prevent PII from customer data flowing through to the LLM context during routine operations? And if one server starts misbehaving at 2am, how do you shut it down without taking down the others? These are the questions Vinkius (vinkius.com) was built to answer. It runs pre-governed MCP servers inside V8 Isolate sandboxes — each call generates a SHA-256 cryptographic audit trail, PII is redacted at the protocol level before reaching the model, and there's a global kill switch per server. The SDK is Vurb.ts, which wraps MCP tool calls with these controls natively rather than as middleware. Your architecture proves the technical feasibility of agent-driven internal operations. The governance layer is what makes it auditable and safe enough to trust at scale. Really impressive operational experiment — would love to see a follow-up on how you handle incidents across 17 concurrent servers.
[@renato_marinho](https://dev.to/renato_marinho) PII Protection: PII redaction is handled at the data layer, not as middleware. Our DB Graph MCP server (detailed in this post) automatically anonymizes query results from production databases — emails become @.com, names become ***, phone numbers/addresses/card numbers are all masked before they ever reach the LLM context. This runs at both the MCP layer and the Lambda layer (dual validation), so even if one layer is compromised, PII doesn't leak. On the observability side, Grafana's log data is also protected — structured logs are designed to exclude PII fields, and Grafana access itself is scoped via Google OAuth with domain restrictions. Servers that don't touch customer data (infrastructure, CI/CD, documentation) simply don't have access to PII-containing databases in the first place — scope separation by design. Observability & Incident Response: Every MCP server is instrumented with OpenTelemetry, and all logs/traces/metrics are aggregated in Grafana. Grafana alerting rules are configured per server — latency spikes, error rate thresholds, and availability checks all trigger Slack notifications automatically. So if a server misbehaves at 2am, the on-call engineer gets a Slack alert immediately. For investigation, we built a Grafana MCP server — meaning Claude Code itself can query logs and metrics. "Show me error logs from the DB Graph MCP in the last hour" returns structured results directly in the AI context. This closes the loop: the same AI that uses the MCP servers can also diagnose issues with them. Independent Deployment: Each server is a separate Cloud Run service with its own Pulumi stack, service account, and IAM roles. Deploying, scaling, or shutting down one server has zero impact on the others. There's no shared runtime or process — they're fully isolated at the infrastructure level.
[@sidclaw](https://dev.to/sidclaw) Great question — "who can call what" is exactly the right thing to worry about. The honest answer is that we use a different approach per server depending on the risk, and stack multiple layers. It's definitely not a "trust-the-agent-and-audit-after" setup. [](#first-a-prerequisite-agents-dont-actually-see-all-17-at-once) First, a prerequisite: agents don't actually see all 17 at once The headline says "17 MCP servers" but a single agent session almost never has all 17 loaded at the same time. For engineer-facing tools, we drop a .mcp.json at the root of each repository listing only the servers that make sense for that repo. A monorepo's .mcp.json might list code-graph, DB graph, CI, Grafana, git workspace, etc., while a standalone frontend repo gets just a single graph-rag entry. The result is that when you start Claude Code in a given workspace, only the relevant tools get loaded, naturally. This is by far the simplest and most effective way to shrink blast radius: we solve the agent's "which of 17 tools should I pick?" problem upstream, at the developer-workflow level, before the model ever has to reason about it. On top of that, each server internally stacks five more layers. [](#layer-1-enforce-readonly-at-the-cloud-api-level-gcloud-aws-gws) Layer 1: Enforce read-only at the cloud API level (gcloud / aws / gws) The gcloud MCP is the clearest example. The user authenticates with the cloud-platform OAuth scope, but when the server actually runs a command, it impersonates a dedicated read-only Service Account and executes the gcloud CLI as that SA. That SA only has roles/viewer, so even if the agent tries to generate something destructive like gcloud run services delete, GCP's IAM itself rejects it with permission denied. Even if our server-side validation has a gap, the cloud API makes it physically impossible. const readonlySa = new gcp.serviceaccount.Account(...); new gcp.projects.IAMMember('readonly-viewer', { role: 'roles/viewer', // ← the real safety net member: serviceAccountMember(readonlySa.email), }); Enter fullscreen mode Exit fullscreen mode aws-server does the same via Google OIDC → AWS STS AssumeRoleWithWebIdentity, swapping into a read-only IAM role. The key point is that the server's own SA is deliberately given no data permissions — all operations happen under the user's token, which has the nice side effect of getting individual email addresses into GCP Audit Log / CloudTrail automatically. [](#layer-2-defense-in-depth-via-input-validation-db-access) Layer 2: Defense in depth via input validation (DB access) DB access MCPs do something more aggressive (SELECT against production DBs), so we add another layer in front. A SQL validator allows only SELECT / SHOW / DESCRIBE / EXPLAIN / WITH, and rejects DROP / TRUNCATE / DELETE / INSERT / UPDATE / ALTER / CREATE and multi-statement queries via regex before the query ever touches a driver. On top of that, the DB users themselves are split into view / edit / delete permission levels, and MCP traffic is pinned to view — a read-only account at the DB grant level. So you get "MCP-side parser rejection × DB-side GRANT" as two independent layers. Slipping through one is plausible, slipping through both is essentially not. [](#layer-3-peruser-acls-in-firestore-writecapable-tools-for-nonengineers) Layer 3: Per-user ACLs in Firestore (write-capable tools for non-engineers) For any MCP server that can write, we keep per-user ACLs in Firestore. The permission doc for the non-engineer "edit and deploy from chat" workspace MCP looks like this: { "email": "user@example.com", "allowedPaths": ["apps/some-service/", "packages/some-domain/"], "allowedStacks": ["api-some-service", "pages-some-service"], "role": "developer" } Enter fullscreen mode Exit fullscreen mode write_file / commit tools do a prefix match against allowedPaths, and deploy tools additionally require allowedStacks membership plus role === 'developer'. Path traversal (..) is rejected by regex. Because both file paths and deployable stacks are whitelisted, a team member can't accidentally reach infra they don't own. One step earlier in the stack, some servers skip individual ACLs and instead gate on users.division / groupName / teamName read from Firestore — a single org-level allow-list per server. When you don't need per-person granularity, it's much cheaper to operate. [](#layer-4-pii-anonymization-at-the-data-layer) Layer 4: PII anonymization at the data layer This is the strongest layer because it doesn't rely on agent judgment at all. When the DB access MCP reads production data, a PII anonymization step strips the result before returning it: email becomes a***@example.com, personal names become 田***, phone numbers become ***-****-1234. It's two-tiered: a global rule set (matching column names like email, phone, password, etc.) plus DB-specific rules keyed by database.table.column. Highly sensitive datasets (HR, etc.) go further — they're locked down at the BQ IAM level to a very small set of owners, so the MCP server's own SA can't even read them. We're not filtering these out in application code; BigQuery itself refuses the query. [](#layer-5-audit-logging-for-every-tool-call) Layer 5: Audit logging for every tool call Every MCP server routes tool executions through a shared package (@cortex/mcp-tool-analytics) that writes to a BigQuery mcp_tool_calls table. Schema: timestamp, server_name, tool_name, user_email, nick_name, department, params, duration_ms, status, result_size, estimated_tokens. One important detail: writes happen via impersonation of a dedicated writer SA, not from each server's own SA, which keeps the log-tampering surface tiny. We roll this up into dashboards — not just for post-hoc audit, but also for usage analysis (finding tools nobody actually calls, so we can delete them). [](#summary) Summary Risk Primary defense Agent picks the wrong server out of 17 Layer 0: per-repository .mcp.json narrows the choice set upstream Agent generates a destructive command Layer 1: IAM physically rejects it Input sanitization gap Layer 2: SQL validator + DB GRANT Out-of-scope resource modification Layer 3: Firestore ACL (paths / stacks / role / org) PII leakage Layer 4: result masking + BQ IAM Detection miss Layer 5: audit log through the shared package The overall design principle is: start read-only, and if you need writes, explicitly whitelist exactly those operations. Because implementers get OAuth and audit logging for free from the shared packages (@cortex/mcp-oauth, @cortex/mcp-tool-analytics), new-server development only needs to focus on the ACL that's specific to that tool. That's a big part of how we got to 17 without the blast radius getting out of hand.