🤖

Slack Bot用の認証付きMCPを作成する

に公開

概要

弊社ではResponses APIをつかって社内用のSlack Botを構築しています。
https://zenn.dev/bizibl_dev/articles/ccaadc7304103d

今回、この Bot に自作の Remote MCP を組み込み、社内 Slack の情報取得・集約を行う機能を追加する構想を検討しました。

ただし、Remote MCP のエンドポイントは Responses API からアクセスする都合上 publicに公開せざるを得ず、以下の懸念点が生じます。

  1. Slack Bot 以外からもアクセス可能になる
  2. 認証情報が漏洩した場合、社内 Slack 情報に不正アクセスされる可能性がある

これらのリスクに対処するため、以下の構成を設計しました。

インフラ

今回採用したアーキテクチャは以下の通りです。

  1. Secret Manager に Remote MCP 用の認証トークンを保存。トークンは一定期間でローテーションする
  2. 社内 Bot (Lambda) は Secret Manager から Remote MCP 用トークンを取得し、Slack Access TokenとともにResponses APIにツール登録
  3. Responses API は Remote MCP 認証トークンとSlack Access Tokenをヘッダーに付与してRemote MCPにリクエスト送信
  4. Remote MCP 側も Secret Manager から同一のトークンを取得し、ヘッダーに付与された値と照合。一致すれば処理を実行
  5. 実際の処理はヘッダーに渡された Slack Access Token を用いて実行

メリット

1.認証トークンは定期的にローテーションされ、かつ AWS 内でのみ取得可能なため、セキュリティが強化される。
2. 万が一認証トークンが漏洩しても、Slack Access Token は Lambda 内に保持されるため、社内情報への直接的なアクセスは不可能。

実装

認証処理

/mcp エンドポイントに対して、認証処理を挟みます。

app.post('/mcp', authenticate, async (req, res) => {
    await transport.handleRequest(req, res, req.body);
});

authenticate 関数では、Secret Manager から取得したトークンとヘッダーを突き合わせます。

export const authenticate = async (req: express.Request, res: express.Response, next: express.NextFunction) => {
    const accessToken = req.headers['mcp-access-token'] as string;

    if (!accessToken) {
      return res.status(400).json({
        jsonrpc: '2.0',
        error: {
          code: -32602,
          message: 'mcp-access-token ヘッダーが必要です',
        },
        id: null,
      });
    }

    const command = new GetSecretValueCommand({
      SecretId: 'authenticate-key'
    });

    const secretResponse = await secretsManagerClient.send(command);
    const expectedToken = secretResponse.SecretString;

    if (!expectedToken || accessToken !== expectedToken) {
      return res.status(400).json({
        jsonrpc: '2.0',
        error: {
          code: -32602,
          message: '無効なアクセストークンです',
        },
        id: null,
      });
    }

    next();
};

処理実行側

処理実行側ではextra.requestInfo.headersからヘッダーを取得し、Slack API 呼び出しに利用します。

server.tool(
  "search-slack-messages",
  "Slackメッセージを検索する",
  {query: z.string().describe("検索クエリ")},
  async (args, extra) => {
    // ヘッダーの取得と検証
    const slackUserToken = extra.requestInfo.headers['slack-user-token'];
    // 残りの処理
  }
);

まとめ

本記事では、Slack Bot から Remote MCP を利用する際の セキュリティ設計パターンを一例として紹介しました。
Remote MCP では一般的に OAuth 2.1 の利用が推奨されていますが、Bot 連携を前提とした場合の標準的なパターンは未だ確立されていません。
今回の方式は、トークンローテーションと多段階のキー管理によって堅牢性を確保しつつ、漏洩時の被害を最小化できる点が特徴です。
もし、さらに洗練された構成やセキュリティパターンをご存知でしたら、ぜひ共有いただけると幸いです


Bizibl では開発エンジニアを絶賛採用しています!カジュアル面談に興味がある方はこちらから!
https://open.talentio.com/r/1/c/bizibl/pages/99945

株式会社Bizibl Technologies テックブログ

Discussion