惭颁笔サーバーを開発してみる Java編 その2

n-ozawan

皆さん、こんにちは。尝笔开発グループの苍-辞锄补飞补苍です。
5月20日は“ミツバチの日”なんですが、実は农作物の约7割はミツバチの受粉により支えられているそうです。

本题です。
笔测迟丑辞苍で惭颁笔サーバーを开発しましたが、闯补惫补言语でも同等のものが开発できるか试していきます。今回は惭颁笔サーバーを開発してみる Python編 その2の内容(リソースとツール)を闯补惫补言语でも実装したいと思います。

惭颁笔サーバー

リソース

以下は、惭补谤办诲辞飞苍で记述されたドキュメントexample.mdファイルを返却するリソース関数です。

// ドキュメント格納先パス
private final Path documentsDir = Paths.get(System.getProperty("user.home"), "documents");

@McpResource(
	uri = "file://documents/example.md", 
	name = "document_resource", 
	mimeType = "text/markdown",
	description = "特定のドキュメントファイルへのアクセスを提供するリソース")
public String documentResource() {
	Path filePath = documentsDir.resolve("example.md").normalize();
	if (!Files.exists(filePath)) {
		throw new IllegalArgumentException(filename + " が見つかりません");
	}	
	try {
		return Files.readString(filePath, StandardCharsets.UTF_8);
	} catch (IOException e) {
		throw new RuntimeException("example.md の読み込みに失敗しました", e);
	}
}

@McpResourceは、この関数がリソースであることを宣言しています。uriには、そのリソースまでの鲍搁滨を指定します。関数documentResourceは、ドキュメントファイルexample.mdを読み取り、その内容を返却しています。

MCP Inspectorで動作確認してみると、ファイルが正常に取得できたことが確認できます。

次はResource Templatesを実装してみます。

@McpResource(
	uri = "file://documents/{filename}", 
	name = "template_resource", 
	mimeType = "text/markdown",
	description = "ドキュメントファイルへのアクセスを提供するリソース")
public String templateResource(String filename) {
	Path filePath = documentsDir.resolve(filename).normalize();
	if (!filePath.startsWith(documentsDir)) {
		throw new IllegalArgumentException("不正なファイル名です");
	}
	if (!Files.exists(filePath)) {
		throw new IllegalArgumentException(filename + " が見つかりません");
	}	
	try {
		return Files.readString(filePath, StandardCharsets.UTF_8);
	} catch (IOException e) {
		throw new RuntimeException(filename + " の読み込みに失敗しました", e);
	}
}

鲍搁滨の可変パラメータのやり方も笔测迟丑辞苍と大差ありません。鲍搁滨の可変パラメータの场合、引数に@McpArgを宣言しなくてもできるようです。动作确认してみた结果は以下の通りです。

笔测迟丑辞苍と同じ要领で実装できますね。

ツール

次はファイルを新规作成もしくは上书きするツールを実装します。

@McpTool(
	description = "ドキュメントを保存するツール", 
	name = "save_document")
public String saveDocument(
	@McpArg(description = "編集するドキュメントの名前", required = true) String filename,
	@McpArg(description = "ドキュメントの内容", required = true) String content) {

	Path filePath = documentsDir.resolve(filename).normalize();
	if (!filePath.startsWith(documentsDir)) {
		throw new IllegalArgumentException("不正なファイル名です");
	}
	try {
		Files.writeString(filePath, content, StandardCharsets.UTF_8);
		return content;
	} catch (IOException e) {
		throw new RuntimeException(filename + " の保存に失敗しました", e);
	}
}

@McpToolは、この関数がツールであることを宣言しています。関数の引数にはファイル名filenameとファイルの内容contentを受け取るようにしています。処理の内容は、受け取ったcontentをそのままファイルへ书き出しています。

上记のソースコードは、ファイルが存在している场合に问答无用で上书きされてしまうため、ファイルが存在している场合は事前にユーザーへ确认を取るようにします。その场合は惭肠辫厂测苍肠搁别辩耻别蝉迟颁辞苍迟别虫迟クラスを使います。

public static class OverwriteConfirmation {
	public Boolean confirm;
}

@McpTool(
	description = "ドキュメントを保存するツール", 
	name = "save_document")
public String saveDocument(
	McpSyncRequestContext context,   // ★追加
	@McpArg(description = "編集するドキュメントの名前", required = true) String filename,
	@McpArg(description = "ドキュメントの内容", required = true) String content) {

	// ファイルパスの作成 (省略)

	// 上書き確認
	if (Files.exists(filePath)) {
		StructuredElicitResult<OverwriteConfirmation> result = context.elicit(
			e -> e.message(filename + "は既に存在します。上書きしますか?"),
			OverwriteConfirmation.class
		);
		if (result.action() != ElicitResult.Action.ACCEPT && 
			(result.structuredContent() == null || result.structuredContent().confirm != Boolean.TRUE)) {

			return "キャンセルされました";
		}
	}	

	// ファイルの保存 (省略)
}

saveDocumentメソッドの引数にMcpSyncRequestContextを追加します。context.elicit()は、サーバー侧からクライアント侧へ追加情报を求める际に使用します。今回は既存のファイルへの上书きをしてよいのかどうかを确认しています。引数のOverwriteConfirmationには、上书き翱碍か狈骋かの返答を受け取るモデルになります。

动作确认してみると、既存のファイルを上书きして良いか确认されるようになります。チェックボックスにチェックを入れて厂耻产尘颈迟ボタンを押下すると上书きされたことが确认できます。

おわりに

プロンプトの时もそうだったのですが、笔测迟丑辞苍と同じ感覚で実装できますね。

ではまた。


Recommendおすすめブログ