生成AI時代のサービス運営管理 – MCP Server for Administratorの実践 –

こんにちは。

ファインディ株式会社 で Tech Lead をやらせてもらってる戸田です。

現在のソフトウェア開発の世界は、生成AIの登場により大きな転換点を迎えています。

GitHub CopilotやClaude Codeなど生成AIを活用した開発支援ツールが次々と登場し、開発者の日常的なワークフローに組み込まれつつあります。

そのような状況の中で先日、弊社から新サービスのFindy AI+がリリースされました。

Findy AI+のα版はリモートMCPサーバーで提供しており、以前の記事でも紹介させていただきました。

tech.findy.co.jp

一般的なWebサービスでは、ユーザー側のアプリケーションの他に、管理者用のアプリケーションを用意することが多くあります。

Findy AI+でも管理者用のアプリケーションを用意しましたが、今回は管理者用のMCPサーバーとして実装することで、専用の画面を必要とせず、自然言語で管理機能を実行できるようにしました。

そこで今回は、この管理者用のMCPサーバーについて深堀って解説していきます。

それでは見ていきましょう!

Findy AI+とは

Findy AI+はGitHub連携・プロンプト指示で生成AIアクティビティを可視化し、生成AIの利活用向上を支援するサービスです。人と生成AIの協働を後押しして、開発組織の変革をサポートします。

GitHub Copilot / Claude Code / Devin / Codexに対応しており、生成AIアクティビティを可視化し、生成AI利活用のボトルネック発見・利活用推進をサポートします。

また、ユーザーがプロンプトで指示して、Findy AI+のMCPサーバー経由で生成AIの利活用状況を定量・定性両面から自動取得します。

Local MCP Server for Administrator

サービスやプロダクトを提供する場合、ユーザー向けの機能だけではなく、サービスの管理者側の機能も必要になることがあります。

このような場合、管理者用の画面を用意するのが一般的です。しかしFindy AI+では管理画面を作らず、Local MCP Serverを介して管理者用のAPIを実行出来るように環境構築を行いました。

昨今のWebフロントエンドの画面は、デザインからHTML/CSS、認証やデータ通信など、1画面を作るために必要な要素が多岐に渡るため、想定以上に時間とコストが掛かることがあります。

しかし、この仕組みにすることで管理者用の画面のデザインや実装をする必要がなく、最低限のAPIとMCPサーバーの実装をするだけで管理機能を用意できるようになりました。

アーキテクチャ

アーキテクチャは非常にシンプルです。

Findy AI+のアーキテクチャ

まずFindy AI+の利用者がRemote MCP Serverに接続して、そこからFindy AI+や、GitHub、Devinなどといった外部APIを実行します。

Findy AI+の管理機能は画面やRemote MCP Serverではなく、運営メンバーのPCにLocal MCP Serverを用意して提供します。Local MCP ServerからFindy AI+のAPIを実行します。

この時、利用者側と管理者側のAPIはエンドポイントレベルで分けており、利用者側から管理者用のAPIを実行出来ないようになっています。

この仕組みを実現することで、管理機能を実装するコストがAPIの実装とLocal MCP Serverの実装コストだけになり、開発コストを大幅に削減しました。

認証

管理者向けのLocal MCP Serverの認証も非常にシンプルです。

運営メンバーのPCで動くため、MCPサーバーの起動時に管理者用APIの実行権限を持たせたアクセストークンを環境変数に付与します。また、APIの実行先を開発用とそれ以外で切り替えできるようにもします。

{
  "inputs": [
    {
      "type": "promptString",
      "id": "ACCESS_TOKEN",
      "description": "MCP Access Token for Findy AI+ Admin",
      "password": true
    },
    {
      "type": "promptString",
      "id": "ENV",
      "description": "production or staging or local",
      "password": false
    }
  ],
  "servers": {
    "admin": {
      "command": "node",
      "args": ["/User/hoge/dist/apps/admin/main.js"],
      "env": {
        "ACCESS_TOKEN": "${input:ACCESS_TOKEN}",
        "ENV": "${input:ENV}"
      }
    }
  }
}

MCPサーバーから管理者用のAPIを実行する際に、そのアクセストークンをhttp headerに付与し、API側で認証を確認します。

import * as https from 'https';
import createFetchClient from 'openapi-fetch';

import type { paths } from './__generated__/schema.js';

const adminApiEnv = process.env['ENV'] || 'local';
const baseUrl =
  adminApiEnv === 'production'
    ? 'https://production.hoge.com'
    : adminApiEnv === 'staging'
    ? 'https://stg.hoge.com'
    : 'https://localhost:8000';
const apiClient = createFetchClientpaths>({
  baseUrl,
  credentials: 'include',
});

const httpsAgent = new https.Agent({ rejectUnauthorized: false });
await apiClient.POST('/api/admin/hoge', {
  params: {
    header: {
      Authorization: `Bearer ${process.env['ACCESS_TOKEN']}`,
    },
  },
  body: {
    text: 'test'
  },
  httpsAgent,
});

このように、APIの実行先の切り替えとアクセストークンを、運営メンバーのローカル環境の環境変数に持たせることで、安全に管理運営できるように整備しました。

利用方法の統一

自然言語を利用して、MCPサーバー経由で管理機能を利用することになりますが、ここで1つ問題が出てきます。

MCPサーバーを実行する際に、運営メンバーが意図した挙動をするプロンプトを入力出来ないケースが想定されます。

この問題を解決するために、MCPサーバーのPromptsを活用することにしました。Promptsを利用することで、定型文を使って動的にプロンプトを作成することが可能になります。

modelcontextprotocol.io

例えば、新しく契約して頂いた企業情報を追加するとします。その場合、次のようなコードでプロンプトそのものを動的に作成できます。

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';

import { z } from 'zod';

const mcpServer = new McpServer({
  name: 'Findy AI+ Admin MCP Server',
  version: '0.0.1',
});

mcpServer.prompt(
  'add_organization',
  'Add organization to Findy AI+',
  {
    last_name: z.string().describe('Last Name(Family Name)'),
    first_name: z.string().describe('First Name'),
    email: z.string().email().describe('Email address'),
    org_name: z.string().describe('Organization name'),
  },
  async ({ last_name, first_name, email, org_name }) => {
    return {
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `Findy AI+に組織を追加してください。

パラメータ:
- 管理者名: ${last_name} ${first_name}
- 管理者メールアドレス: ${email}
- 組織名: ${org_name}
`,
          },
        },
      ],
    };
  }
);

実際に実行すると、プロンプトを作るために必要な情報を聞かれます。

プロンプトのinput

指示に従って内容を入力していくと、最終的に動的なプロンプトが作成されます。

出力されたプロンプト

あとはそのまま実行するだけで、誰が実行しても同じ結果になります。

Elicitation

MCPサーバーで管理機能を実現するうえで避けて通れない問題がもう1つあります。基本的にMCPサーバーとのやり取りは一方通行だということです。

MCPサーバーとのやり取りの基本的なシーケンス図はこうなります。

基本的なシーケンス図

このシーケンス図から分かる通り、リクエストを投げたらレスポンスが返ってくるといった、一般的な流れです。

しかし入力した企業名を間違っていたままプロンプトを実行してしまった場合、間違った情報のままでAPIが実行されてしまうのを止める手段がありません。

そこで活躍するのが、以前に他の記事でも紹介したMCPのElicitationという機能です。

tech.findy.co.jp

Elicitationを利用することで、MCPサーバーとのやり取りのシーケンス図は次のように変わります。

Elicitationを使ったシーケンス図

confirmとapproveのフローが増えているのが分かるかと思います。MCPサーバーとMCPクライアントの通信中に、ユーザーに追加情報を要求できるようになるのです。

Elicitationを使った確認フローの実装例は次のようになります。

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { apiClient } from '@admin/api-client';
import * as https from 'https';
import { z } from 'zod';

const mcpServer = new McpServer({
  name: 'Findy AI+ Admin MCP Server',
  version: '0.0.1',
});

mcpServer.tool(
  'create_org',
  'Create a new organization',
  {
    org_name: z.string().describe('Organization name'),
    email: z.string().email().describe('Email address'),
    last_name: z.string().describe('Last name(Family name)'),
    first_name: z.string().describe('First name'),
  },
  {
    title: 'Create Organization Tool',
  },
  async ({ org_name, email, last_name, first_name }) => {
    const confirmation = await mcpServer.server.elicitInput(
      {
        message: `組織 "${org_name}" を作成しますか?\n管理者: ${last_name} ${first_name} (${email})`,
        requestedSchema: {
          type: 'object',
          properties: {
            confirm: {
              type: 'string',
              title: '確認',
              description: 'Type "yes" to confirm or "no" to cancel',
              enum: ['yes', 'no'],
            },
          },
          required: ['confirm'],
        },
      },
      { timeout: 30000 }
    );

    if (
      confirmation.action !== 'accept' ||
      confirmation.content?.['confirm'] !== 'yes'
    ) {
      return {
        content: [
          {
            type: 'text',
            text: 'Organization creation cancelled by user.',
          },
        ],
      };
    }

    const httpsAgent = new https.Agent({ rejectUnauthorized: false });
    const { response, data } = await apiClient.POST('/api/admin/hoge', {
      params: {
        header: {
          Authorization: `Bearer ${process.env['ACCESS_TOKEN']}`,
        },
      },
      body: {
        org_name,
        email,
        last_name,
        first_name,
      },
      httpsAgent,
    });

    if (response.status !== 201 || !data) {
      return {
        content: [
          {
            type: 'text',
            text: `Error: Unexpected response status ${response.status}`,
          },
        ],
      };
    }

    const { id, name, user_id, user_first_name, user_last_name, user_email } = data;
    return {
      content: [
        {
          type: 'text',
          text: `Organization created successfully:
- ID: ${id}
- Name: ${name}
- User ID: ${user_id}
- User Name: ${user_last_name} ${user_first_name}
- User Email: ${user_email}`,
        },
      ],
    };
  }
);

実際にGitHub Copilot ChatでElicitationを実行した様子を見てみましょう。

まずは先程紹介したプロンプトを実行します。Toolの実行許可を尋ねられるので許可しましょう。

プロンプトを実行

すると、次のような確認ステップが出力されます。

ElicitationのRespond

このRespondボタンを押下すると、次のような確認モーダルが表示されます。

ElicitationのConfirm

今回のケースだとyesを選択するとAPIが実行され、noを選択するとそこで処理が終了となります。

このように、MCPサーバーとの通信中にユーザーに選択肢を要求して処理を分岐したい時にElicitationは大きな威力を発揮します。

まとめ

いかがでしたでしょうか?

今回の内容は先日開催した Findy AI Meetup in Fukuoka #2 でも紹介させてもらいました。

こちらがその時の資料となります。是非参考にしてみてください。

ファインディではMCPの有用性にいち早く気づき、検証を続けてきました。

その結果、開発効率を上げるだけではなく、今回紹介したようにサービスに埋め込んだり、プロダクトの作り方、提供の仕方までもが大きく変わりました。

MCPの可能性はどんどん広がっています。是非みなさんのMCP活用法も教えてください。

現在、ファインディでは一緒に働くメンバーを募集中です。

興味がある方はこちらから ↓
herp.careers




元の記事を確認する

関連記事