GitHub から 備品管理アプリを取得し、既存の Service を変えずに MCP Tool を追加して IBM Bob から呼び出します。
「既存の Java システムを Bob から使えるようにする」といっても、Servlet、JSP、Service、DAO を AI 用に作り直す必要はありません。既存の業務ロジックを呼ぶ MCP 専用の入口 を追加すればよい構成にできます。
この記事では、Java 25・Jakarta EE 10・IBM WebSphere Application Server Liberty / Open Liberty へ移行済みの備品管理アプリを出発点に、MCP Tool を二つ追加します。
-
searchEquipment:備品一覧と在庫状況を返す -
getActiveLoanSummary:貸出中の備品と期限超過件数を返す
完成後、Bob に「利用可能な備品一覧を教えて」「返却期限を過ぎた備品はある?」と入力すると、既存 Java の Service が DB を照会した結果を Bob が説明します。
この記事で分かること
- GitHub の Java 25 移行済みアプリを起点にする方法
- Liberty の
mcp-1.0を有効にする変更 - 既存
ItemService/LoanServiceを再利用する Tool の作り方 - Bob への接続と確認方法
- 既存 Web 画面を置き換えない、並行追加の考え方
MCP の概念と最小サンプルは、先にこちらを参照してください。
前提
| 必要なもの | 用途 |
|---|---|
Podman と podman-compose
|
アプリケーションの起動 |
| IBM Bob | MCP Client として Tool を呼び出す |
| Git | サンプルの取得 |
サンプルの実行イメージは OSS の Open Liberty です。同じ Liberty 機能を使うため、IBM WebSphere Application Server Liberty でも動作する構成です。
mcp-1.0 は Liberty 26.0.0.9 で GA(正式提供)になりました。ベータ版の mcpServer-1.0 / <mcpServer> ではなく、GA 版の mcp-1.0 / <mcp> を使います。
1. 出発点のアプリを取得して起動する
まず、MCP を含まない Java 25 移行済みアプリを取得します。
git clone https://github.com/TSA0001/equipment-management-java25.git
cd equipment-management-java25
BUILDAH_FORMAT=docker podman-compose up --build -d
ブラウザで次を開きます。
http://localhost:9083/equipment-management/items
ログインには、サンプルの一般利用者 user1 / user1234 または管理者 admin / admin123 を使えます。
ここで備品一覧の画面が表示できれば、既存アプリの確認は完了です。
このリポジトリーのアプリ固有コードは Apache-2.0 で公開しています。H2 や Liberty などの外部コンポーネントについては THIRD-PARTY-NOTICES.md を参照してください。
2. MCP は既存機能の置き換えではなく、入口の並行追加
今回の変更後も、既存利用者は従来の Web 画面を使い続けます。Bob 向けにだけ EquipmentTools というクラスを追加し、既存の Service を呼びます。
既存利用者 IBM Bob
↓ ↓
Web画面 → AuthFilter → Servlet / JSP MCP → EquipmentTools ← 今回追加
│ │
└───────┬────────────┘
↓
ItemService / LoanService ← 既存の業務ロジック
↓
DAO → H2
この例では、既存の次のメソッドをそのまま再利用します。
// 既存の ItemService
public List<Item> search(ItemSearchCriteria criteria) throws ServiceException
// 既存の LoanService
public List<Loan> findActiveLoans() throws ServiceException
Servlet、JSP、DAO は変更しません。MCP Tool に DB の SQL を直接書くのでもなく、既存 Service の業務ルールを迂回するのでもありません。
3. Maven 依存関係を追加する
pom.xml の <properties> に、次を追加します。
<cdi.api.version>4.0.1</cdi.api.version>
<mcp.server.api.version>1.0.0</mcp.server.api.version>
<liberty.mcp.api.version>1.0.117</liberty.mcp.api.version>
続いて <dependencies> に追加します。Liberty が実行時に提供する API なので、スコープは provided です。
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<version>${cdi.api.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mcpjava</groupId>
<artifactId>mcp-server-api</artifactId>
<version>${mcp.server.api.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.openliberty.api</groupId>
<artifactId>io.openliberty.mcp</artifactId>
<version>${liberty.mcp.api.version}</version>
<scope>provided</scope>
</dependency>
4. Liberty に MCP エンドポイントを追加する
src/main/liberty/config/server.xml の <featureManager> に、CDI と MCP を追加します。
<featureManager>
<feature>pages-3.1</feature>
<feature>cdi-4.0</feature>
<feature>mcp-1.0</feature>
<feature>monitor-1.0</feature>
</featureManager>
既存の <application /> を開始・終了タグ形式に変更して、その内側へ MCP のパスと説明を追加します。
<application id="equipment-management"
location="equipment-management.war"
type="war">
<mcp path="/mcp">
<info name="Equipment Management"
version="1.0.0"
description="備品管理システムの読み取り専用照会ツール" />
</mcp>
</application>
このアプリでは、コンテキストパスと合わせた MCP URL は次です。
http://localhost:9082/equipment-management/mcp
以降は Web 画面用のポートを 9083 から 9082 に変えます。compose.yaml の公開ポートを次のように変更してください。
ports:
- "9082:9080"
5. AI に返す項目だけを持つ戻り値を追加する
DB の Item や Loan をそのまま返さず、AI に必要な項目だけを定義します。src/main/java/com/example/equipment/mcp を作り、次の 4 ファイルを追加します。
// EquipmentItem.java
package com.example.equipment.mcp;
public record EquipmentItem(String managementNo, String itemName,
String categoryName, String storageLocation,
String status, String statusLabel) {
}
// EquipmentSummary.java
package com.example.equipment.mcp;
import java.util.List;
public record EquipmentSummary(int itemCount, String statusFilter,
List<EquipmentItem> items) {
}
// ActiveLoan.java
package com.example.equipment.mcp;
public record ActiveLoan(String managementNo, String itemName,
String userName, String plannedReturnDate, boolean overdue) {
}
// ActiveLoanSummary.java
package com.example.equipment.mcp;
import java.util.List;
public record ActiveLoanSummary(int activeLoanCount, int overdueLoanCount,
List<ActiveLoan> activeLoans) {
}
ここが、LLM へ渡す量を抑える境界です。たとえば Item に原価や社内メモがあっても、EquipmentItem に含めなければ Tool の結果には入りません。
6. 既存 Service を呼ぶ EquipmentTools を追加する
同じ mcp パッケージへ EquipmentTools.java を追加します。@Tool が付いた public メソッドだけが、Bob から発見・実行できる Tool になります。
package com.example.equipment.mcp;
import java.sql.Date;
import java.util.ArrayList;
import java.util.List;
import com.example.equipment.model.Item;
import com.example.equipment.model.ItemSearchCriteria;
import com.example.equipment.model.ItemStatus;
import com.example.equipment.model.Loan;
import com.example.equipment.service.ItemService;
import com.example.equipment.service.LoanService;
import com.example.equipment.service.ServiceException;
import jakarta.enterprise.context.ApplicationScoped;
import org.mcpjava.server.tools.Tool;
import org.mcpjava.server.tools.ToolArg;
@ApplicationScoped
public class EquipmentTools {
// 既存 Service は CDI Bean ではないため、このサンプルでは new する。
private final ItemService itemService = new ItemService();
private final LoanService loanService = new LoanService();
@Tool(name = "searchEquipment", title = "備品一覧・在庫状況",
description = "備品の一覧を返します。利用可能な在庫・保管中備品、貸出中、修理中、廃棄の状況を確認するときに使います。status を省略すると全件、AVAILABLE を指定すると利用可能な備品だけを返します。",
structuredContent = true)
public EquipmentSummary searchEquipment(
@ToolArg(name = "status", required = false,
description = "AVAILABLE=利用可能(保管中・在庫)、LOANED=貸出中、REPAIRING=修理中、DISPOSED=廃棄。省略時は全状態。") String status)
throws ServiceException {
ItemSearchCriteria criteria = new ItemSearchCriteria();
ItemStatus itemStatus = normalizeStatus(status);
if (itemStatus != null) {
criteria.setStatus(itemStatus.name());
}
List<Item> items = itemService.search(criteria); // 既存処理を再利用
List<EquipmentItem> equipment = new ArrayList<EquipmentItem>();
for (Item item : items) {
equipment.add(new EquipmentItem(item.getManagementNo(), item.getItemName(),
item.getCategoryName(), item.getStorageLocation(),
item.getStatus().name(), item.getStatusLabel()));
}
return new EquipmentSummary(equipment.size(),
itemStatus == null ? null : itemStatus.name(), equipment);
}
@Tool(name = "getActiveLoanSummary", title = "貸出中備品の状況",
description = "現在貸出中の備品を一覧で返します。備品名、管理番号、利用者、返却予定日、期限超過かどうかを確認したいときに使います。",
structuredContent = true)
public ActiveLoanSummary getActiveLoanSummary() throws ServiceException {
List<Loan> loans = loanService.findActiveLoans(); // 既存処理を再利用
List<ActiveLoan> activeLoans = new ArrayList<ActiveLoan>();
int overdueCount = 0;
for (Loan loan : loans) {
if (loan.isOverdue()) {
overdueCount++;
}
activeLoans.add(new ActiveLoan(loan.getManagementNo(), loan.getItemName(),
loan.getUserName(), formatDate(loan.getPlannedReturnDate()), loan.isOverdue()));
}
return new ActiveLoanSummary(activeLoans.size(), overdueCount, activeLoans);
}
private String formatDate(Date date) {
return date == null ? null : date.toString();
}
private ItemStatus normalizeStatus(String status) throws ServiceException {
if (status == null || status.trim().isEmpty()) {
return null;
}
String normalized = status.trim().toUpperCase();
if ("利用可能".equals(status) || "保管中".equals(status) || "在庫".equals(status)) {
normalized = ItemStatus.AVAILABLE.name();
}
try {
return ItemStatus.fromCode(normalized);
} catch (IllegalArgumentException e) {
throw new ServiceException("status は AVAILABLE、LOANED、REPAIRING、DISPOSED のいずれかを指定してください。");
}
}
}
既存アプリの Service がすでに CDI Bean の場合は、new ItemService() / new LoanService() の代わりに @Inject を使います。
7. getActiveLoanSummary 用のデータを用意する
searchEquipment は初期データだけで確認できます。一方、getActiveLoanSummary で「貸出中」「期限超過」を試すには、貸出中のデータが必要です。
最も分かりやすい方法は、既存の Web 画面から 2 件を貸し出すことです。
- 管理者
adminでログインする - 備品一覧から 2 件を貸し出す
- 片方の返却予定日を過去日に、もう片方を未来日にする
これで既存 Web 画面が作成した LOANS レコードを、MCP Tool が LoanService.findActiveLoans() 経由で読むことを確認できます。MCP 専用の DB や SQL は不要です。
初期データとして組み込みたい場合は、最初の起動前に src/main/resources/db/seed.sql へ、状態が LOANED の備品と対応する LOANS レコードを追加しても構いません。このサンプルは DB が未作成のときだけ seed を投入するため、すでに起動済みなら Web 画面から登録する方法が安全です。
8. デモ用に /mcp を認証フィルタから通す
既存の AuthFilter が未ログイン利用者を /login へリダイレクトする場合、Bob の通信も止まります。デモでは isPublicPath に /mcp を追加します。
return "/login".equals(path)
|| "/logout".equals(path)
|| "/health".equals(path)
|| "/mcp".equals(path) // デモ用
|| path.startsWith("/css/");
この認証除外はデモ限定です。 本番では /mcp を匿名公開せず、Liberty / Jakarta Security で認証し、利用者のロールごとに Tool の利用可否を制御してください。最初は読み取り専用 Tool だけを公開するのが安全です。
9. 起動して Bob から確認する
変更後のアプリを起動します。
BUILDAH_FORMAT=docker podman-compose up --build --force-recreate -d
Bob の MCP 設定に、次を追加します。
{
"mcpServers": {
"liberty-equipment": {
"type": "streamable-http",
"url": "http://localhost:9082/equipment-management/mcp"
}
}
}
Bob 側でサーバーが「接続済み」になったら、新しいチャットを開いて次のように入力します。
| Bob への入力 | 実行される Tool |
|---|---|
利用可能な備品一覧を教えて |
searchEquipment |
現在貸出中の備品を教えて |
getActiveLoanSummary |
返却期限を過ぎた備品はある? |
getActiveLoanSummary |
Tool を追加・修正した後は、Bob の MCP サーバーを再接続し、新しいチャットで試してください。古いチャットには以前取得した Tool 一覧が残ることがあります。
まとめ
このハンズオンで加えたのは、MCP の設定、Tool クラス、AI に返す専用の戻り値、デモ用の経路設定です。
既存の Web 画面、Servlet、JSP、Service、DAO を MCP 専用に置き換えてはいません。EquipmentTools を並行して追加し、既存 ItemService / LoanService を使うため、従来の業務ロジックを保ちながら Bob 向けの入口を増やせます。
次の段階では、Tool の戻り値を用途ごとにさらに絞り、認証・認可と監査ログを追加して本番利用へ近づけます。