🧞‍♂️

カスタムして理解するMCP Server ~Slack MCP Server編~

に公開

近年、AI関連の情報を調べていると「MCP Server」という言葉を耳にする機会が増えてきたのではないでしょうか。
私自身もこれまでにさまざまなMCP Serverを試してきました。たとえば、Blenderと連携させて3Dモデリングに挑戦したり、Ableton Liveと組み合わせて作曲を試みたりと、いろいろな用途でMCPを活用しています。

「AIのUSBハブ」とも称されるこの仕組みは、その表現のとおり非常に柔軟かつ拡張性の高い技術であり、大きな可能性を感じています。
一方で、個人的な所感としては「もう一歩で理想に届きそうなのに……」という、痒いところに手が届かないもどかしさもあるのが正直なところです。

現在、さまざまなMCP Serverが公開されていますが、「自分が使いたい特定のサービスに対応していない」といったケースも少なくないのではないでしょうか。
加えて、サードパーティ製のMCPはセキュリティ面から導入が難しいと感じる場面もあるかと思います。

そこで本記事では、公式のMCP Serverのコードをベースに、内部でどのような処理が行われているのかを詳しく読み解き、自作のMCP Serverを構築できるようになること を目指します。

同様の内容を扱った記事もすでに存在しているかと思いますが、題材選びに迷っている方や、「ゼロから作るのは難しそう」と感じている方に向けて、実際にカスタムした具体例を交えながら解説しています。ぜひ参考にしていただければ幸いです。

MCP Serverとは何かをおさらい

MCP(Model Context Protocol)は、Anthropic社が開発したオープンな標準プロトコルであり、AIアシスタントと外部のデータソースやツールを安全かつ双方向に接続するための仕組みです。

このプロトコルにより、AIモデルはコンテンツリポジトリ、業務ツール、開発環境などと直接連携できるようになり、より適切で関連性の高い応答を生成することが可能になりました。

https://www.anthropic.com/news/model-context-protocol

今回はその中でもSlackの公式MCPのコードを覗き見る形で、MCPがコードで何をしているかを確認していきましょう。

公式Slack MCP Serverはこちらです。
https://github.com/modelcontextprotocol/servers/tree/main/src/slack

Slack MCPをカスタムしてみました

MCP Serverの仕組みをより深く理解するため、まずは公式のSlack MCPのコードを読み解き、自分なりにカスタマイズを試みました。

その結果として作成したカスタム版のMCP Serverが、以下のリポジトリに公開しています
https://github.com/motsuo373/mcp-servers-slack-extension

本カスタムでは、slack_search_messages という機能を追加しました。
これは、Slack上で特定のキーワードによるメッセージ検索を可能にするもので、公式のMCP Serverには備わっていなかった機能です。

もともとのSlack MCPでは、特定のチャンネルやスレッドに対してデータの読み書きを行うことはできましたが、「検索」という観点での機能は提供されていませんでした。
そのため、Slack内の情報をAIが効率的に取得・活用できるようにするために、検索機能の実装に至りました。

なお補足ですが、この検索機能は、過去の公式Slack MCPに一時的に存在していたものの、現在は削除されています。
今回はそのコードを復元し、改めて利用可能な形に整備したものです。
また、この機能を利用するには Slackのユーザートークン(User Token) が必要です。
おそらく、ユーザートークンに関するセキュリティ上の制約が厳しくなったことが、当該機能が公式から削除された一因ではないかと推察しています。

カスタムした内容の概略

今回は、既存のMCPに対して以下のような機能を追加しました。

リクエストハンドラーの型定義

index.ts
interface SearchMessagesArgs {
  query: string;
  count?: number;
}

MCPツール側で持たせる用の定義部分

index.ts
// 中略
const searchMessagesTool: Tool = {
  name: "slack_search_messages",
  description: "Search for messages across channels",
  inputSchema: {
    type: "object",
    properties: {
      query: {
        type: "string",
        description: "The search query",
      },
      count: {
        type: "number",
        description: "Number of results to return (default 5)",
        default: 5,
      },
    },
    required: ["query"],
  },
};

Slack API処理

index.ts
// 中略
  async searchMessages(query: string, count: number = 5): Promise<any> {
    const searchQuery = this.excludeDMsFromSearch ? `${query} -in:@` : query;
    const params = new URLSearchParams({
      query: searchQuery,
      count: count.toString(),
    });

    const response = await fetch(
      `https://slack.com/api/search.messages?${params}`,
      { headers: this.userHeaders }
    );

    return response.json();
  }

リクエストハンドラー

index.ts
// 中略
      case "slack_search_messages": {
        const args = request.params
          .arguments as unknown as SearchMessagesArgs;
        if (!args.query) {
          throw new Error("Missing required argument: query");
        }
        const response = await slackClient.searchMessages(
          args.query,
          args.count
        );
        return {
          content: [{ type: "text", text: JSON.stringify(response) }],
        };
      }

以上が、公式のSlack MCP Serverに対して追加した主なコードです。
見てわかるとおり、複雑な処理はほとんど追加しておらず、自然言語による機能説明やプロパティの定義を行い、それに基づいて適切なAPIを呼び出している構成となっています。

細かく見ると、特定のAPIを呼び出すためのAPIトークンを追加する処理なども存在しますが、本質的には以下の点を押さえることが重要であると分かります。

  • どのような機能を提供するのか
  • その機能に必要なプロパティは何か
  • 実行結果がどのような内容で、AIにどう伝えるのか

これらを的確に設計することで、MCP Serverを柔軟に拡張していくことが可能になります。

カスタムSlack MCPの設定方法

今回はCursorを用いてMCPができているか確認します。

Slack APIの設定方法と必要なトークンについて
  1. Slack API にアクセスし、「Create an App」→「From scratch」を選択
  2. 任意のアプリ名と対象ワークスペースを選び「Create App」をクリック
  3. 左メニューから「OAuth & Permissions」を開き、「Bot Token Scopes」に以下を追加
    chat:write(メッセージ送信)
    channels:read(チャンネル情報)
    channels:history(履歴閲覧)
    users:read(ユーザー情報)
    team:read(チーム情報)
    reactions:write(リアクション追加)
  4. 続けて「User Token Scopes」以下を追加
    search:read(ユーザとして検索を読む機能を追加)
  5. ページ上部の「Install to Workspace」をクリックし、許可する
  6. 表示される Bot Token(xoxb-〜) をコピー
  7. 表示される User Token(xoxp-〜)をコピー
  8. チームIDをコピー(Slackをブラウザで開き、URL内 T0XXXXXXX が チームID)
  9. それぞれを下記のcursorのenvに埋め込む

cursorのmcp.jsonでは下記のような設定をすることで動作します。

{
  "mcpServers": {
    "slack": {
      "command": "node",
      "args": [
        "/Users/canly/ghq/github.com/motsuo373/mcp-servers-slack-extension/dist/index.js"
      ],
      "env": {
        "SLACK_BOT_TOKEN": "xoxb-1234567890123-XXXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXX",
        "SLACK_TEAM_ID": "ABCDEFGHIJKLM",
        "SLACK_USER_TOKEN": "xoxp-12345678901234-1234567891234-012345678901-XXXXXXXXXXXXXXXXXXXXXXXXX"
      }
    },
  }
}

カスタムしたSlack MCPを使ってみよう!

通常のSlack MCPですでにある機能は問題なく動作するかと思いますので、今回作成したslack_search_messagesを使ってみましょう。

社内のドメイン知識や顧客のデータにはマスキングをしています。

Slackで、コーヒーに関する話題を取得してまとめましょう。
プロンプト結果

コメントアウトした内容は社内の実際にコミュニケーションされていた内容で背景情報を補足しています

Slackのメッセージを検索した結果から、コーヒーに関する主な話題をまとめさせていただきます:
**オフィスのコーヒー環境**
オフィスにコーヒーマシンが設置 //社内で実際にある福利厚生です。
2024年3月からトライアル導入
コーヒー、紅茶、ココアが利用可能
トライアル期間中は無料で利用可能 
**社内のコーヒー文化**
Motsuo Coffee
もつおさんが運営する社内コーヒーサービス  //これは私がジョークで始めた過去のslackを拾っています
浅煎りコーヒーを提供
挽きたての美味しいコーヒーが飲める
**おすすめのコーヒーショップ**
// 社内のtimesで美味しいコーヒー屋さんについて議論したものが拾われています。
XXXXXXX COFFEE(XXXXXXコーヒー)
神保町・銀座に店舗あり
味とお値段のバランスが良い
浅煎りコーヒーが特徴
YYYYYYY COFFEE(YYYYYYコーヒー)
中目黒駅から徒歩1分
古民家を改装した静かな雰囲気
浅煎りコーヒーが美味しい
デートにおすすめ
**福利厚生関連**
某Tコーヒー連携
フクリー(弊社サービス)にて某Tコーヒーが掲載開始
ドリンクが1日何杯でも10%割引で利用可能
その他の話題
コーヒースクールやワークショップの開催情報
リーンコーヒー(Lean Coffee)という会議形式の紹介
社員の中にはラテアートができる方もいる
コーヒーの淹れ方や道具(ケトルなど)についての話題
全体的に、カンリー(※弊社)社内ではコーヒーを楽しむ文化が根付いており、特に浅煎りコーヒーが人気のようです。また、福利厚生の一環としてもコーヒーが取り入れられており、社員の方々のコーヒーライフをサポートする取り組みが行われています。

Slackでエラーについて調べさせてみて、会社のドメイン知識的なものから原因が何かを特定してみます。

GRANT ALL PRIVILEGES ON meo_api_testing.* TO 'root'@'%';
というエラーが出ました。slackで検索を交えながら何が原因か、参照すべきスレッドのリンクなどを明示しながら修正方法を提案してください。
プロンプト結果
Slackの検索結果から、このエラーについていくつかの重要な情報が見つかりました。
このエラーの主な原因と解決方法について説明させていただきます:
エラーの原因:
ERROR 1726 (HY000): Storage engine 'MyISAM' does not support system tables.

[mysql.user] というエラーは、MySQLのシステムテーブルがMyISAMエンジンを使用していることが原因です。
現代のMySQLでは、システムテーブルはInnoDBを使用することが推奨されています。
解決方法:
以下の手順で対応することができます:
# 1. まずMySQLにrootユーザーでログイン
docker exec -it db mysql -u root -p
//  中略 社内のドメイン情報の設定を入れることで対処できました。
参考となるSlackスレッド:
詳細な設定手順が記載されているスレッド:
https://motsuotest.slack.com/archives/CXXXXXXXXXX/p1234567890123456
追加の注意点:
Docker環境を使用している場合、コンテナのIPアドレスが変わる可能性があるため、'%'を使用して全てのホストからのアクセスを許可することが推奨されます。
セキュリティ上の理由から、本番環境では適切なIPアドレス制限を設けることを推奨します。
もし上記の手順を実行しても問題が解決しない場合は、MySQLのバージョンやストレージエンジンの設定を確認する必要があるかもしれません。その場合は、追加の調査をさせていただきますので、お知らせください。

Slackのデータを用いられ、回答されていることがわかりました。

MCPの仕組みをもう少し理解する

これまでMCP Serverについては大まかな理解にとどまっていたため、今回はその構造や動作の中身をもう少し掘り下げてみます。

また、下記の公式のドキュメントを参考にしています。
https://modelcontextprotocol.io/introduction

MCPは、クライアント・サーバーアーキテクチャ
を採用しており、以下のような構成要素で成り立っています:

  • ホストは、接続を開始するLLMアプリケーション(Claude DesktopやIDEのようなもの)
  • クライアントは、ホストアプリケーションの内部で、サーバーとの1:1の接続を維持する
  • サーバーは、クライアントにコンテキスト、ツール、プロンプトを提供する。

MCPでは、JSON-RPC 2.0 をフォーマットとして通信をしています。
MCPのライフサイクルは下記のようになっています

「通常のメッセージ交換が開始される」以降、MCPの規格に則って通信が行われることで、AI側は必要な情報をJSON形式で受け取り、ユーザーに適切な応答を提供できる仕組みになっています。

コードベースを確認すると、setRequestHandler 関数内で server クラスがJSON-RPCに従ってメッセージを処理していることがわかります。
https://github.com/motsuo373/mcp-servers-slack-extension/blob/40727e7f9a5f427d32e7fb307a47f6bebf02fa90/index.ts#L437-L458

続いて、プロンプトの定義についても詳しく見ていきます。
MCPのツールとしてAIに渡す必要があるプロンプトは、以下のように設定します。参考

{
  name: string;              // プロンプトにおける一意な識別子
  description?: string;      // 人間が読む用の説明
  arguments?: [              // オプショナルな引数リスト
    {
      name: string;          // 引数の識別子
      description?: string;  // 引数の説明
      required?: boolean;    // 引数が必要かどうか
    }
  ]
}

実際のコードでは、以下のように定義されたプロンプトをAIと通信する際に用いることで、MCPの機能を効果的に伝達していることが確認できます
https://github.com/motsuo373/mcp-servers-slack-extension/blob/40727e7f9a5f427d32e7fb307a47f6bebf02fa90/index.ts#L185-L203

このように実装を追っていくと、MCPの構成要素や動作は非常にシンプルであり、あとは自然言語による説明を通じて、AIエージェントが適切に判断を下せるようになっていることが理解できます。

さいごに

今回は、Slack MCP Serverのカスタマイズを通じて、MCPの仕組みや構成について解説しました。
一見シンプルなカスタム内容に見えるかもしれませんが、今回のような機能追加によって、AIに対してより細かなコンテキストを提供し、意図した結果を引き出すことが可能になります。さらに、コードとの連携によって、処理の自動化やエラー回避といった実用的な対応にもつながります。

実装を追いながら感じたのは、MCPの構造は非常にシンプルで理解しやすいという点です。もちろん、より深く学ぶには公式ドキュメントの参照が推奨されますが、JavaScriptにある程度慣れている方であれば、特別な前提知識がなくても自作のMCP Serverに挑戦できるのではないかと感じました。

今回はAPIベースの比較的シンプルな構成でしたが、同様にAPI連携が可能なサービスであれば、MCPを活用した独自ツールの構築も十分現実的です。
少しでも「自分のユースケースに合ったMCPを作ってみたい」と感じた方の参考になれば幸いです。

カンリーテックブログ

Discussion