はじめに

こんにちは、FANTECH本部前田 です。
サイバーエージェントでは、AIツールへの投資を積極的に行っており、2025年6月には 年間で約4億円のAI活用予算をエンジニアに支援 することを発表しています。

私が所属するチームでも、Claude CodeやCursorなど、AIエージェントを利用した開発が導入されており、開発フローの中にAIがいます。
一方、全職種がAIを活用して然るべきフェーズに来ていますが、担当業務よっては個人の業務最適化に閉じているケースが多いです。

個々人がAI活用について知り、自身の業務を最適化していくことがファーストステップですが、組織としてのAI活用の成果創出も考えていく必要があります。
$200の支援を組織に還元していくためにも、AIを利用しやすい土壌整備はエンジニアのミッションの1つになってきていると感じています。

AIイネーブリングという考え方もありますが、もっと平たく「AIを民主化する」ことをチーム内で意識しています。
$200の利用レベルとして見据えるべきライン

本記事でご紹介するMCP Serverも、AIの民主化を見据えて取り組んだ事例になります。

管理画面のMCP Server化

エンジニアはAI活用の土壌が整ってきていますが、他職種や業務内容によっては、AIの恩恵を受けにくい状況もあります。
特に、業務の根幹をなすと言っても過言ではない管理画面ですが、データの入稿は人手で行っています。

管理画面自体へのAI活用は進めていましたが、今はその将来性についても議論が必要だと考えています。
「検索するな、AIに聞け」と言われ始めている世の中で、私のチームでは管理画面という重要なコンポーネントがWeb UIに閉じていることに課題感を持ちました。
この課題に対して実際に行ったことはシンプルですが、管理画面をMCP Server化しました。

例えば、サービス内の記事をAIとの対話の中で入稿したり、番組情報や商品情報を更新したり、これまでWeb UI上で行っていたことを徐々にAIネイティブに遂行できることを目指しています。
実際に開発しているMCP ServerをClaudeに連携し、ペイパービューのデバッグデータを作成してみた例がこちらです。

管理画面MCP-Serverのデモ1 管理画面MCP-Serverのデモ2 管理画面MCP-Serverのデモ3

ペイパービューデータの入稿は手順が多く、

  1. 番組データ(イベント)の作成
  2. 価格を設定する商品データの作成
  3. ペイパービューとしてそれらのデータを紐づけ

を行う必要があります。 デモ画像は非常に単純な例ですが、複数のデータをAIとの対話の中で一気通貫に作成できることは、非常に有意義に感じています。

MCP Serverとしてのサポート状況

ペイパービューデータの作成を含めて、現在は以下の機能をサポートしています。

  • 【Tool】ライブ配信番組データ(Get/Create/Update)
  • 【Tool】商品データ(Get/Create/Update)
  • 【Tool】ペイパービューデータ(Get/Create/Update)
  • 【Tool】VOD番組データ(Get/Create/Update)
  • 【Tool】サービス内記事(Get/Create/Update)
  • 【Tool】サービス内お知らせ・予約送信(Get/Create/Update)
  • 【Tool】プッシュ通知送信・予約送信(Get/Create/Update)
  • 【Prompt】複数のデータにまたがるデータ作成のプロンプト・ガイドライン

工夫ポイントとしては、これらのツールの動作を安定させるために、補助的なツールを実装しています。 番組の公開時刻をUnixtimeで設定する必要があるため、文字列の時刻情報(RFC3339)からUnixtimeへの変換を用意していたり、海外からサービスを利用されるケースもあるため、辞書を利用した翻訳ツールを用意したりしています。

これらに加えて、現在でも追加のツール開発を続けています。

技術選定と実装

アクセス制御

アクセス制御には、既存のWeb UIで利用していた管理画面ユーザーの情報を利用しています。
既に存在する管理画面ユーザーの権限セットをそのまま利用しているため、MCP Server経由で実行できることと、Web UIで実行できることには差異がありません。

Google OAuthを利用しており、コードフローの中で管理画面にアクセスする権限のないユーザーの場合はコードフローを中断します。
アクセストークンを発行した後も、MCP Clientからの各リクエストに対して権限チェックを行っています。
権限は、各データに対して権限なし、読み取り、書き込みの3段階で分かれており、ユーザー単位で細かい権限管理が可能です。

OAuthコードフローの実装

MCP Client向けのOAuthのフローについては、modelcontextprotocolに仕様の記載 があります。
一般的なWebアプリケーション向けのコードフローとの差異については、記事でまとめられている方も多いためここでは省略します。
ここでご紹介している管理画面MCP ServerはClaude上で利用していますが、ClaudeやMCP Inspectorから利用する場合はDCR(Dynamic Client Registration)が必須であることが注意点です。

SDKとProtobufでのスキーマ集約管理

私のチームはGoを利用しているため、GoでMCP Serverを実装するためのライブラリとして mark3labs/mcp-go を利用しています。
※現在は、modelcontextprotocol公式からもSDK が提供されています。

MCP ServerのToolを実装するためには、MCP Serverに送信すべき情報をMCP Clientに伝えるためのスキーマと、GoのHTTPハンドラー内で利用するリクエスト型定義の2つの情報が必要になります。
例えば、非常に単純な例として、送信されたテキストをそのままMCP Clientに返すToolの実装では、 EchoArgsechoTool で重複したリクエスト情報を2度定義しています。
実運用されているプロダクトのマスターデータは、より多くのフィールドやビジネスロジックを制御するフラグなどが含まれており、情報の二重管理はネガティブな側面が強いです。

package mcp

import (
	"context"

	"github.com/mark3labs/mcp-go/mcp"
	mcpserver "github.com/mark3labs/mcp-go/server"
)

func registerEcho(srv *mcpserver.MCPServer) {
	srv.AddTool(echoTool, mcp.NewTypedToolHandler(echoHandler))
}

type EchoArgs struct {
	Text string `json:"text"`
}

var echoTool = mcp.NewTool("echo",
	mcp.WithDescription("Echoes the input text"),
	mcp.WithString("text",
		mcp.Required(),
		mcp.Description("Text to echo"),
	),
)

func echoHandler(_ context.Context, _ mcp.CallToolRequest, args EchoArgs) (*mcp.CallToolResult, error) {
	return mcp.NewToolResultText(args.Text), nil
}

対応策として、Protobufでリクエストスキーマを定義し、MCP Clientに伝えるためのスキーマとGoのリクエスト型を生成するための、protocのプラグインを実装しました。
requireddescription などのフィールドオプションをProtobufのメッセージ定義に付与しておきます。

message Article {
  string id           = 1 [(mcpschema.field_options) = { description: "Article id. This field will be automatically set by the system." }];
  string category     = 2 [(mcpschema.field_options) = { required: true, description: "Article category. Must be one of news, event, ..." }];
  string title        = 3 [(mcpschema.field_options) = { required: true, description: "Article title." }];
  string body         = 4 [(mcpschema.field_options) = { required: true, description: "Article body." }];
  string image_url    = 5 [(mcpschema.field_options) = { required: true, description: "Article image url." }];
  int64  publish_time = 6 [(mcpschema.field_options) = { required: true, description: "Article publish time." }];
  bool   draft        = 7 [(mcpschema.field_options) = { description: "Article draft flag. This field will be automatically set by the system." }];
}

コンパイル結果は以下のように、MCP Client向けスキーマとGoのリクエスト型が生成されています。

// article.mcp.pb.go
var ArticleSchema = map[string]any{
	"type": "object",
	"properties": map[string]any{
		"id": map[string]any{
			"description": "Article id. This field will be automatically set by the system.",
			"type":        "string",
		},
		"category": map[string]any{
			"description": "Article category. Must be one of news, event, ...",
			"type":        "string",
		},
		"title": map[string]any{
			"description": "Article title.",
			"type":        "string",
		},
		"body": map[string]any{
			"description": "Article body.",
			"type":        "string",
		},
		"image_url": map[string]any{
			"description": "Article image url.",
			"type":        "string",
		},
		"publish_time": map[string]any{
			"description": "Article publish time.",
			"type":        "integer",
		},
		"draft": map[string]any{
			"description": "Article draft flag. This field will be automatically set by the system.",
			"type":        "boolean",
		},
	},
	"required": []string{
		"category",
		"title",
		"body",
		"image_url",
		"publish_time",
	},
}

// article.pb.go
type Article struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Id          string `protobuf:"bytes,1,opt,name=id,proto3" json:"id"`
	Category    string `protobuf:"bytes,2,opt,name=category,proto3" json:"category"`
	Title       string `protobuf:"bytes,3,opt,name=title,proto3" json:"title"`
	Body        string `protobuf:"bytes,4,opt,name=body,proto3" json:"body"`
	ImageUrl    string `protobuf:"bytes,5,opt,name=image_url,json=imageUrl,proto3" json:"image_url"`
	PublishTime int64  `protobuf:"varint,6,opt,name=publish_time,json=publishTime,proto3" json:"publish_time"`
	Draft       bool   `protobuf:"varint,10,opt,name=draft,proto3" json:"draft"`
}

このひと工夫によって、チーム内でのTool実装速度が格段に上がり、集約管理によって部分的な変更漏れも発生しなくなりました。
また、本番環境で利用する場合は、利用者が慣れるまでは必ず下書き状態でデータを作成する方針としています。
管理画面上でプレビューし、問題なければデータを公開状態に変更する運用を想定してのリスクヘッジの仕組みです。

そして、これらの実装はバックエンドのコードベースでモノレポ管理されており、ビジネスロジックへの追従漏れが静的解析を通じて防止されます。
管理画面には実装されている機能が、MCP Serverには未実装という状況を防止できる管理方針を採っています。

今後想定しているユースケース

AIとの対話の中で管理画面を操作できるということは、CursorやClaude Codeで実装した機能に対応するデバッグデータを、開発時のプロンプトをもとに作成できるということです。
何度も同じデータを作成する場合も、AIエージェントに依頼すれば履歴から同じデータを複製できますし、過去のデータを参照させてコピーさせることも可能です。
その結果、機能デバッグの効率はかなり向上するはずです。

また、本番環境へのデータ入稿においても、このMCP Serverを利用していく予定です。
MCP ServerのPromptを準備することで、円滑なデータ入稿のためのプロンプトエンジニアリングを覚える必要性が薄まりますし、多言語化を行うかどうかなど、データを入稿する上での統一性も高まります。

ClaudeのモバイルアプリもMCP Serverに対応したため、管理画面自体がレスポンシブ対応していない場合でも、状況に応じてMCP Serverからデータを参照して確認することもできます。
今後、MCPの普及につれてユースケースも増えていくと考えています。

生成AIガイドラインについて

生成AIを活用してAIネイティブなデータ入稿が可能になる一方で、安全・便利に活用する方法は利用者が知っておく必要があります。
サイバーエージェントでは、社内で運用されている生成AIガイドラインに考慮すべきリスクについて記載されています。特に、正確性を損なう情報や、権利侵害リスクのある情報を入稿しないために、利用者にはデータを確認する責任があります。

加えて、プロンプトインジェクションなど生成AIをターゲットとした攻撃も今後増えていく可能性が高く、利用者のITリテラシーの向上や、何か異変を感じた場合の対応フローまでを整備していく必要もあります。利用者1人1人が「自分の身は自分で守る」ことも重要ですが、AIを民主化していくためにも、組織として整備していくガイドラインは今後も必要になってくると考えています。

まとめ

管理画面は性質上、1つのプロダクトとして高い品質を維持することが難しい側面があると思っています。
オーナーシップの所在、事業上の開発優先度、プレイグラウンド化、etc

極論、管理画面をデータの検索や確認フローにのみ利用し、入稿はAIエージェントから実施するような世界線もあり得ると考えています。
エンジニア目線では、管理画面自体の実装が薄くなることでメンテナンスコストも下がりますし、ユーザー目線だとAIとの対話の中でデータ入稿まで完了するため、インターフェースが統一されるメリットもあります。

今後、エンジニアとしてAIを民主化し、エンジニア以外の職種がAIを活用するための工夫に加えて、AI時代の技術投資について考え実践を重ねていきたいと考えています。
以上、管理画面をMCP Server化する事例と生成AIの活用についてご紹介しました。 1人のエンジニアとして、管理画面のMCP Server化とその先をどう考えているか、誰かのご参考になれば幸いです。

アバター画像
2020年新卒入社のエンジニア。FANTECH本部所属。会社でコーヒー紅茶を淹れて周りに無差別提供します。