はじめに
「自作のJakarta EEアプリとLLMを連携させたい」と考えたとき、まず想像するのは以下のような構成かと思います。
上記の例で、アプリケーションサーバーにSpringBootを使用している場合は modelcontextprotocol.io にMCPサーバーの実装例も紹介されているため、比較的簡単にアプリに組み込めます。またOpen Libertyを使用している場合もmi-ta氏の解説記事を参考に実装できそうです。
ただ、いずれもアプリケーションサーバーに由来する独自のAPIを使用しているようで、Eclipse GlassFishのような他のアプリケーションサーバーを使用している場合は以下のような対応が必要になります。
- Jolokia MCPサーバーのような既成のMCPサーバーを組み込む(アプリとのアダプタは自作する)
- modelcontextprotocol.ioが公開しているSDKを利用してMCPサーバーをアプリに組み込む
- JAX-RS(現 Jakarta RESTful Web Services)でMCPサーバー相当の処理を自作する
筆者もどうせやるならと下2つにトライしてみたのですが、通信まわりの処理の実装がどうも難しい…。これはMCPやSSEにもう少し詳しくなったら再挑戦したいと考えていますが、ここでふと下記のようなことを考えました。
「わざわざサーバー側にLLM連携のためにMCPサーバーを作りこまなくても、AIアプリ側にRESTクライアントを組み込めれば、RESTエンドポイント経由でアプリの情報取ったり操作したりできるのでは?」
RESTエンドポイントを持ちHTTPリクエストで情報を取ってきたり操作したりできるサービスなど世の中にありふれているため既に1億人くらい同じこと考えて実践していそうな話ではありますが、この記事は前述のようなサーバー側のJakarta EEアプリにMCPサーバーを組み込む方法ではなく、下図のようにAIアプリ側にRESTクライアントとして動作するMCPサーバーを組み込む方法で、既存アプリに一切手を加えずに無理やりLLM連携させてみたというお話になります。

用意したアプリ
- simple-login-app : RESTエンドポイントを持つ簡易のWebアプリのプロジェクト (war)
- simple-login-app-client : AIアプリに組み込むMCPサーバーのプロジェクト (jar)
Webアプリ(simple-login-app)の中身
Eclipse GlassFish上に配備の後、ブラウザからテキストボックスに名前を入れ「ログイン」ボタンを押すと、Helloメッセージが表示され、ログイン記録がcsvファイルに書き出されるだけのもの。
起動手順
-
https://adoptium.net/temurin/releases/?version=21からEclipse Temurin JDK 21をダウンロードして適当なパスに展開 - 環境変数
JAVA_HOMEに上記のJDKのパスを設定 -
https://glassfish.org/download_gf7.html から
Eclipse GlassFish 7.1.0, Jakarta EE Platform, 10 (zip)を選択してダウンロードして適当なパスに展開 - simple-login-app内の
mvnwを実行、target下に作成されるsimple-login-app.warファイルを、<GlassFishの展開パス>\glassfish7\glassfish\domains\domain1\autodeploy下に配置 -
<GlassFishの展開パス>\glassfish7\glassfish\bin\asadmin start-domainを実行し、GlassFishを起動
ブラウザからhttp://localhost:8080/simple-login-app/にアクセスすると、以下のような画面が表示されます
名前を入れて「ログイン」ボタンを押すと、記録が以下のようにcsvファイルに出力されます
2025-12-22 00:26:33,Hiroki
2025-12-22 00:26:38,Rebecca
2025-12-22 00:29:11,Alan
2025-12-22 00:54:43,Hiroki
また、上記の記録をもとに情報を取得可能ないくつかのRESTエンドポイントを用意しています。(これらを後のMCPサーバー実装で仮想的にAIツール化してAIアプリから参照します)
| パス | |
|---|---|
| /api/login/history | ログイン記録のリストを返却 |
| /api/login/last | 最後にログイン情報を返却 |
| /api/login/count | 誰が何回ログインしているかの情報を返却 |
| /api/login/most-frequent | 最も頻繫にログインした人の名前とその回数を返却 |
| /api/login/statistics | 雑多な統計情報を返却 |
| /api/login/user/{name} | 特定のユーザーのログイン記録を返却 |
MCPサーバー(simple-login-app-client)の中身
MCPサーバーは面倒な処理をだいたいmodelcontextprotocol.ioが公開しているSDKが隠蔽してくれているので、下記3つのクラスを作るだけ
MyMcpServerMainクラス
MCPサーバーをkickするためのMainクラス
public class MyMcpServerMain {
public static void main(String[] args) {
// JSON処理に使用するObjectMapperのラッパー。起動時にクラスローダーから使えるJSON-B実装(?)を探してくれるよう
var jsonMapper = McpJsonMapper.getDefault();
// 通信層のプロバイダ(今回はSTDIO用)を作成
var transportProvider = new StdioServerTransportProvider(jsonMapper);
// 同期型のMCPサーバーを起動(実装例に従って変数に格納しているが、必須なのだろうか...)
var syncServer = MyMcpServer.syncServer(transportProvider);
}
}
MyMcpServerクラス
MCPサーバーのオブジェクト。これも小さいクラス
public class MyMcpServer {
// AIツール化したい操作リストを予めstatic finalで固めたが、
// AIアプリからのリクエストごとにサーバーを起動しているわけでもなさそうなので
// 後のsyncServer()内で作ってもよいかもしれない
private static final List<McpServerFeatures.SyncToolSpecification>
MY_MCP_TOOLS = MyMcpTools.syncToolSpecifications();
// 同期型のMCPサーバーをビルド。ビルダーでちょこちょこ設定を追加していく形なのでとても簡単
public static McpSyncServer syncServer(McpServerTransportProvider transportProvider) {
return McpServer.sync(transportProvider)
.serverInfo("simplelogin-mcp-server", "0.0.1")
.capabilities(McpSchema.ServerCapabilities.builder()
.resources(true, false)
.tools(true)
.prompts(false)
.build())
.tools(MY_MCP_TOOLS)
.build();
}
}
MyMcpToolsクラス
RESTクライアントをエンドポイントごとに個別のAIツールとして実装するクラス。ここが肝。最終的にAIツールの実装リストをList<SyncToolSpecification>としてMCPサーバーに渡します
public class MyMcpTools {
private static final String SERVER_ENDPOINT = System.getProperty("server.endpoint", "");
private static final McpJsonMapper jsonMapper = McpJsonMapper.getDefault();
// ここでリストにまとめたAIツールは最終的にMCPサーバービルド時に渡す
public static List<SyncToolSpecification> syncToolSpecifications() {
return List.of(getLoginHistory(), getLastLogin(),
getLoginCount(), getMostFrequentUser(),
getStatistics(), getUserHistory());
}
ツールの定義例(前述の/api/login/user/{name}エンドポイントをAIツール化する例):
private static SyncToolSpecification getUserHistory() {
return SyncToolSpecification.builder()
.tool(Tool.builder()
// AIアプリがツールを特定するための名前と説明。本来はちゃんと目的や得られる情報を含めるべきと思われる
.name("get-user-login-history")
.description("get user login history")
// AIアプリがツールに対して行うリクエストのJSONスキーマ。APIの変数名からだいたいの用途は想像可能
// 少し古いバージョンではStringで直接スキーマを流し込めたようだが、本記事作成時点で最新の0.17.0ではオブジェクト定義になっている
.inputSchema(new McpSchema.JsonSchema(
"object",
Map.of("userName", String.class),
List.of("userName"),
null, null, null
))
.build())
// AIツールの実装を登録
.callHandler(MyMcpTools::getUserHistory)
.build();
}
// AIツールの実装本体。
private static McpSchema.CallToolResult getUserHistory(McpSyncServerExchange exchange, CallToolRequest request) {
// AIアプリからのリクエストに含まれるパラメータ要素を取得
String userName = (String) request.arguments().get("userName");
try {
String encodedUserName = URLEncoder.encode(userName, StandardCharsets.UTF_8).replace("+", "%20");
// RESTエンドポイントにHTTPリクエストを送信し、Stringに格納
String result = httpRequest(SERVER_ENDPOINT + "/api/login/user/" + encodedUserName);
var content = jsonMapper.writeValueAsString(result);
return CallToolResult.builder().addTextContent(content).build();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 指定のエンドポイントにGETリクエストを送るHTTPクライアント
private static String httpRequest(String endpoint) {
try (HttpClient client = HttpClient.newHttpClient()) {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(endpoint))
.GET()
.build();
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
return res.body();
} catch (IOException | InterruptedException ex) {
throw new RuntimeException(ex);
}
}
ちゃんと起動できるかの確認
javaでjarを起動した後、コンソールが待機状態になれば起動成功です。(inspectorを使う方法もあるのですが、筆者は上手く使いこなせませんでした...)
java -Dserver.endpoint=http://localhost:8080/simple-login-app -jar <simple-login-appプロジェクトを展開したパス>\simple-login-app-client\target\simple-login-app.jar
Claude for DesktopでLLM連携
Claude for Desktopをダウンロード&インストールし、設定ファイル(%APPDATA%\Claude\claude_desktop_config.json)に以下の要領で定義を追加してClaudeを再起動すると有効になります(Claudeを終了しても何故かプロセスが残る場合があるようで、そのままでは再起動しても設定が更新されないので、その場合はタスクマネージャーなどから強制停止します)
{
+ "mcpServers": {
+ "login-server": {
+ "command": "java",
+ "args": [
+ "-Dserver.endpoint=http://localhost:8080/simple-login-app",
+ "-jar",
+ "<simple-login-appをcloneしたパス>\\simple-login-app-client\\target\\simple-login-app.jar"
+ ]
+ }
+ },
"preferences": {
"menuBarEnabled": false,
"legacyQuickEntryEnabled": false
}
}
上記の設定ファイルでMCPサーバー名に指定した"login-server"をキーワードに、チャットで情報を聞き出してみると..
無事AIツールの定義越しにRESTリクエストが発行されてアプリから情報が取れてそうですね
最後に
既にRESTでサービスを提供しているアプリなら、本記事で紹介した方法であれば最低3つクラスを用意するだけでLLMから呼び出せるようになるため、簡単に"なんちゃってLLM連携"が実現可能です。(ToolオブジェクトをAIアプリに許可する要求ぶん生成しないといけないのが若干面倒ですが、それはどこにMCPサーバーを用意する場合でも似たようなものなので仕方ない...)
HTTPのクライアント部分も、今回は標準APIで雑に書きましたが、Apache Commons HttpClientなどを使用すれば実用的な実装も簡単にできると思います。
modelcontextprotocol.ioが公開しているSDKも今回初めて触ったのですが、STDIOの同期処理を書くぶんには特に迷うこともなくサクサク作れたので今後もAIツール作りが捗りそうです。



