1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Fun with MCP Proxy 2 - 複数のAPIをコンソリしたMCPサーバを建てる

Last updated at Posted at 2025-12-09

本エントリはKong Advent Calendar 2025の12/10の投稿です。

はじめに

Kong Gatewayのリリースの度に様々なプラグインが追加されていますが、2025年10月に登場したKong Gateway 3.12には少し特殊なAI MCP Proxyプラグインが発表されました。このプラグインは一般的なREST APIへのリクエストではなく、MCPによるリクエストに対応する為のプラグインです。このプラグインについてはこちらのエントリで紹介しています。

本エントリでは、AI MCP Proxyプラグイン(以降MCP Proxy)を使ったサンプルを通して、このプラグインの利用方法やアプローチについて紹介します。

尚、本エントリ、ならびに続くMCP Proxyのサンプル環境はGitHubのこちらのレポジトリに用意しいますので、実機で試して頂くことも可能です。しかしながら、本エントリの目的は、読み物としてMCP Proxyがどう言うものかを理解してもらう事を目的としています。なのでなるべく詳しく説明する形で書きます。

デモ環境を実際にお試しになるには、環境にはDocker Compose、Kongの設定にはdecKが必要となります。

MCP ProxyのプラグインはKong Gatewayのエンタープライズ機能の一つである為、利用にはライセンスが必要です。Kong Konnectを利用すると1ヶ月間お試しいただけます。

デモ環境 - Account APIとTransaction API

Account/Transaction Demo - Overview

このデモでは、Account APIとTransaction APIという2つのサービスから特定の処理のみ選択/統合して1つのMCPサーバを構築します。複数のAPIをコンそりする目的である為、OpenWeather APIのデモのケースとは異なり、複数のKong AI MCP Proxyプラグインを組み合わせて利用します。具体的には、各REST APIとのマッピング/MCP定義の用にそれぞれconversion-onlyモードを1つずつ、MCPサーバとしてのエンドポイントをKong上に公開する為のlistenerモードを1つ、計3つのMCP Proxyを組み合わせた構成となります。

デモ環境にはデモスクリプトと、APIアクセスに必要なInsomnia Collectionを用意していますので参照してください。

AccountサービスとTransactionサービスにアクセス

今回のデモ環境では、既にAccountサービスとTransactionサービスがそれぞれコンテナとして動いています。Accountサービスは8081、Transactionサービスは8082からアクセス出来ます。これらサービスへのアクセスは、conversion-transactions.yamlに定義しているので

deck gateway sync conversion-transactions.yaml

の適用によりKong Gateway経由でのアクセスが可能となります。

提供しているInsomnia Collectionには、これらサービスに対するリクエストを含んでいます。
transactions-insomnia.png
Accountsで始まるリクエストはAccountサービス用、Transactionsで始まるリクエストはTransactionサービス用です。TransactionサービスはAccountサービスに依存しており、「誰に対する」という対象としてAccountサービスで再版されるアカウントIDを併せて定義する必要があります。Transactionサービスで登録したトランザクションはAccountにおける残高に反映されます。一方、Accountサービスでもトランザクションを管理する事が可能(Debit/Creditで指定)ですが、あくまでAccountサービス内の機能である為Credit/Debitを利用した場合Transactionサービスのレコードとして登録されません。

今回のサンプルでは、意図的にAccountサービスのCredit/DebitはMCPにはToolとして公開せず、かつAccountとTransactionの2つのサービスに対して1つのMCPサーバを提供するアプローチを取ります。

Kongの設定

Kongの設定はconversion-transactions-mcp.yamlに纏めています。

先ほど同様、decKコマンドの実行でプラグインが適用されます。

deck gateway sync conversion-transactions-mcp.yaml

MCPサーバとToolの集約

エージェントから見てMCPサーバは1つ。そのMCPサーバには4つのToolが登録されていますが、Toolは2つのサービスに跨って定義されています。

MCPはBFFとは異なりますが、特定のエージェントに対して幾つかのサービスへのアクセスを一元的に提供出来るメリットが大きいケースも多くあります。特に、現時点ではToolの数が多いとLLMでのTool利用の判断精度が下がるといったリサーチや、実際にLLM側でTool参照数のリミットが設けられている事もあり、ある程度エージェントに参照させるToolの制御をMCPサーバ側で担う必然性はあります。

MCP Proxyの定義

複数のAPIに対して適切な粒度でTool化するにはどうするか?まずはMCP Proxyの設定内容をより細かく説明します。上記Account/Transactionサービスアクセス用のyamlとの差分であるプラグイン定義のみ抽出してみます。

plugins:
...
- name: ai-mcp-proxy
  route: accounts-mcp
  config:
    mode: listener
    server:
      tag: accounts-mcp
    logging:
      log_payloads: false
      log_statistics: true
- name: ai-mcp-proxy
  tags:
  - accounts-mcp
  route: accounts
  config:
    mode: conversion-only
    tools:
    - annotations:
        title: Create an account
      description: Create an account with specified initial values.
      method: POST
      request_body:
        content:
          application/json:
            schema:
              type: object
              properties:
                type:
                  description: Type of the account. It could be either savings or checking.
                  type: string
                  enum:
                  - savings
                  - checking
                initial_balance: 
                  description: Initial amount in US dollars. Only the natural number is allowed.
                  type: integer
                  format: int64
              required: [type, initial_balance]
    - annotations:
        title: List all accounts
      description: Get a list of accounts with id, account type, and balance.
      method: GET
- name: ai-mcp-proxy
  route: transactions
  tags:
  - accounts-mcp
  config:
    mode: conversion-only
    tools:
    - annotations:
        title: Create a transaction
      description: Create a new credit transaction for a given Account ID.
      method: POST
      path: /transactions/{accountId}
      parameters:
      - name: accountId
        in: path
        description: Account ID for the transactions.
        schema:
          type: string
        required: [accountId]
      request_body:
        content:
          application/json:
            schema:
              type: object
              properties:
                amount:
                  description: The amount to be credited.
                  type: integer
                  format: int64
                description:
                  description: Description of this credit transaction.
                  type: string
                transaction_type:
                  description: Type of the transaction. It could be either credit or debit.
                  type: string
                  enum:
                  - credit
                  - debit
              required: [amount, description, transaction_type]
    - annotations:
        title: List all transactions
      description: Get a list of transaction with id, date, amount, description, and transaction type for a given account.
      method: GET
      path: /transactions/{accountId}
      parameters:
      - name: accountId
        in: path
        description: Account ID for the transactions.
        schema:
          type: string
        required: [accountId]

listener

listnerの役割はMCPサーバのエンドポイントの定義と、UpstreamにあたるREST APIとの紐付けにありますが、定義は至ってシンプルです。

plugins:
...
- name: ai-mcp-proxy
  route: accounts-mcp
  config:
    mode: listener
    server:
      tag: accounts-mcp
    logging:
      log_payloads: false
      log_statistics: true
...

listnerは特定のRouteと紐付ける事により、そのRouteをMCPサーバとします。具体的には、ここではroute: accounts-mcpと直接Routeを指定しています。実際には他のプラグイン同様このRouteに対してMCP Proxyを適用しているだけではあるのですが、効果としてそのRouteがMCPサーバとなります。

次にconfig.server.tagとして、このlistenerにタグを指定しています。listner自体の定義にはどのREST APIと繋がっているかの定義はありません。逆に、REST APIとの変換処理において、このタグを参照する事によりlistenerと紐付けます。

conversion-only

文字通り、REST APIとMCPのプロトコル変換のみを行うMCP Proxyです。今回のサンプルでは2つ定義されていますが、1つだけ取り出して見てみます。

plugins:
...
    - name: ai-mcp-proxy
+     tags:
+     - accounts-mcp
      route: accounts
      config:
        mode: conversion-only
        tools:
        - annotations:
            title: Create an account
          description: Create an account with specified initial values.
          method: POST
          request_body:
            content:
              application/json:
                schema:
                  type: object
                  properties:
                    type:
                      description: Type of the account. It could be either savings or checking.
                      type: string
                      enum:
                      - savings
                      - checking
                    initial_balance: 
                      description: Initial amount in US dollars. Only the natural number is allowed.
                      type: integer
                      format: int64
                  required: [type, initial_balance]
        - annotations:
            title: List all accounts
          description: Get a list of accounts with id, account type, and balance.
          method: GET
...

conversion-onlyモードのMCP Proxyの定義は、conversion-listenerモードを利用したサンプルと非常に似ています。ここでの定義におけるconfig.routeはUpstreamであるREST APIを指しており、これがlistenerの時と異なります。

違いはtagsにて、listenerのタグを指定している点です。(Diffとして表示)conversion-onlyの場合、REST APIとMCPの変換は出来ますが、MCPサーバとして機能している訳ではないのでlistnerとの紐付けが必要です。ここでは前述のlistnerで定義したconfig.server.tagと同じタグを付加する事によりlistenerを指定しています。

もう一点、conversion-onlyでのタグ指定はtagsとなっており、複数のlistnerと紐づける事が可能です。これにより、REST/MCPの変換は1箇所に定義した上、異なるMCPサーバに紐付けてToolとして後悔する事も可能です。

MCP Inspectorの起動

では実際にMCPサーバにアクセスしてみます。エージェントとMCPサーバの通信ステップを把握する為、本サンプルではMPC Inspectorを使ってそのステップを確認します。MCP Inspectorを起動すると

npx @modelcontextprotocol/inspector

MCP Inspectorがブラウザに立ち上がります。上記のアドレスを指定の上、ConnectとするとMCPサーバとの対話が開始されます。
transaction-inspector.png

Tools Discovery (tools/list)

エージェントはまず、接続したMCPサーバから利用可能なToolの仕様を取得します。

{
  "method": "tools/list",
  "params": {}
}

MCPサーバー側(つまりMCP Proxy)からのレスポンスは以下です。

{
  "tools": [
    {
      "name": "create-a-transaction",
      "description": "Create a new credit transaction for a given Account ID.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "path_accountId": {
            "description": "Account ID for the transactions.",
            "type": "string"
          },
          "body": {
            "additionalProperties": false,
            "properties": {
              "amount": {
                "description": "The amount to be credited.",
                "type": "integer",
                "format": "int64"
              },
              "description": {
                "type": "string",
                "description": "Description of this credit transaction."
              },
              "transaction_type": {
                "enum": [
                  "credit",
                  "debit"
                ],
                "type": "string",
                "description": "Type of the transaction. It could be either credit or debit."
              }
            },
            "type": "object",
            "required": [
              "amount",
              "description",
              "transaction_type"
            ]
          }
        },
        "required": [
          "body",
          "path_accountId"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "title": "Create a transaction"
      },
      "id": "19ef3dc2-98cd-4393-9334-a628ae978461"
    },
    {
      "name": "create-an-account",
      "description": "Create an account with specified initial values.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "body": {
            "additionalProperties": false,
            "properties": {
              "type": {
                "enum": [
                  "savings",
                  "checking"
                ],
                "type": "string",
                "description": "Type of the account. It could be either savings or checking."
              },
              "initial_balance": {
                "description": "Initial amount in US dollars. Only the natural number is allowed.",
                "type": "integer",
                "format": "int64"
              }
            },
            "type": "object",
            "required": [
              "type",
              "initial_balance"
            ]
          }
        },
        "required": [
          "body"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "title": "Create an account"
      },
      "id": "59bc9181-736c-47a9-a1d0-73a56456363e"
    },
    {
      "name": "list-all-accounts",
      "description": "Get a list of accounts with id, account type, and balance.",
      "inputSchema": {
        "type": "object",
        "properties": {},
        "additionalProperties": false
      },
      "annotations": {
        "title": "List all accounts"
      },
      "id": "59bc9181-736c-47a9-a1d0-73a56456363e"
    },
    {
      "name": "list-all-transactions",
      "description": "Get a list of transaction with id, date, amount, description, and transaction type for a given account.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "path_accountId": {
            "description": "Account ID for the transactions.",
            "type": "string"
          }
        },
        "required": [
          "path_accountId"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "title": "List all transactions"
      },
      "id": "19ef3dc2-98cd-4393-9334-a628ae978461"
    }
  ]
}

ここでは2つのMCP Proxy(つまり2つのサービス)に跨って定義した4つのToolが一覧として返されます。エージェント観点では、これらToolがどの様な構成になっているかを意識せず、Toolを1つのグループとして扱う事が出来ます。

Tool Execution (tools/call)

エージェントは利用できるToolを特定した後、定義されたパラメータを指定して実行します。ここではlist-transactionsを選択し、Transactionに必要なアカウントIDを指定の上リストを取得します。

Request

{
  "method": "tools/call",
  "params": {
    "name": "list-all-transactions",
    "arguments": {
      "path_accountId": "9233a8e8-d18b-4ff1-be1c-62d850f99cd4"
    },
    "_meta": {
      "progressToken": 1
    }
  }
}

Response

{
  "content": [
    {
      "type": "text",
      "text": "[{\"transaction_id\":\"tx-d03682aa33\",\"date\":\"2025-11-24T12:36:31.208434Z\",\"amount\":5000.0,\"description\":\"Salary Payment\",\"transaction_type\":\"credit\"},{\"transaction_id\":\"tx-f6767e2100\",\"date\":\"2025-11-24T12:36:48.520480Z\",\"amount\":12000.0,\"description\":\"Annual Bonus Payment\",\"transaction_type\":\"credit\"},{\"transaction_id\":\"tx-144f9123e7\",\"date\":\"2025-11-24T12:37:30.653307Z\",\"amount\":-300.0,\"description\":\"Purchase - Amazon\",\"transaction_type\":\"debit\"},{\"transaction_id\":\"tx-9748b56bee\",\"date\":\"2025-11-24T12:37:45.110398Z\",\"amount\":-800.0,\"description\":\"Monthly Tuition\",\"transaction_type\":\"debit\"}]"
    }
  ],
  "isError": false
}

Accounts - tools/call

レスポンスのJSONの中身は以下となります。

[
  {
    "transaction_id": "tx-d03682aa33",
    "date": "2025-11-24T12:36:31.208434Z",
    "amount": 5000,
    "description": "Salary Payment",
    "transaction_type": "credit"
  },
  {
    "transaction_id": "tx-f6767e2100",
    "date": "2025-11-24T12:36:48.520480Z",
    "amount": 12000,
    "description": "Annual Bonus Payment",
    "transaction_type": "credit"
  },
  {
    "transaction_id": "tx-144f9123e7",
    "date": "2025-11-24T12:37:30.653307Z",
    "amount": -300,
    "description": "Purchase - Amazon",
    "transaction_type": "debit"
  },
  {
    "transaction_id": "tx-9748b56bee",
    "date": "2025-11-24T12:37:45.110398Z",
    "amount": -800,
    "description": "Monthly Tuition",
    "transaction_type": "debit"
  }
]

おわりに

本エントリでは、MCP Proxyを利用して複数REST APIを束ねたMCPサーバを構築しました。listnerモードとconversion-onlyモードで指定した複数のMCP Proxyをタグで紐付けることにより、MCPサーバとUpstreamのREST APIを多対多の関係性で柔軟に組み合わせる事が可能となります。

次回は、実際にVolcanoSDKを利用したAIエージェントと、AI Proxy Advanced、MCP Proxyを組み合わせた実践的なサンプルをご紹介します。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?