MCP サーバーアノテーション

MCP サーバーアノテーションは、Java アノテーションを用いて MCP サーバー機能を宣言的に実装する方法を提供します。これらのアノテーションにより、ツール、リソース、プロンプト、補完ハンドラーの作成が簡素化されます。

サーバーアノテーション

@McpTool

@McpTool アノテーションは、メソッドを自動 JSON スキーマ生成を備えた MCP ツール実装としてマークします。

基本的な使い方

@Component
public class CalculatorTools {

    @McpTool(name = "add", description = "Add two numbers together")
    public int add(
            @McpToolParam(description = "First number", required = true) int a,
            @McpToolParam(description = "Second number", required = true) int b) {
        return a + b;
    }
}

高度な機能

@McpTool(name = "calculate-area",
         description = "Calculate the area of a rectangle",
         annotations = McpTool.McpAnnotations(
             title = "Rectangle Area Calculator",
             readOnlyHint = true,
             destructiveHint = false,
             idempotentHint = true
         ))
public AreaResult calculateRectangleArea(
        @McpToolParam(description = "Width", required = true) double width,
        @McpToolParam(description = "Height", required = true) double height) {

    return new AreaResult(width * height, "square units");
}

リクエストコンテキストを使用する場合

ツールは、高度な操作のためにリクエストコンテキストにアクセスできます。

@McpTool(name = "process-data", description = "Process data with request context")
public String processData(
        McpSyncRequestContext context,
        @McpToolParam(description = "Data to process", required = true) String data) {

    // Send logging notification
    context.info("Processing data: " + data);

    // Send progress notification (using convenient method)
    context.progress(p -> p.progress(0.5).total(1.0).message("Processing..."));

    // Ping the client
    context.ping();

    return "Processed: " + data.toUpperCase();
}

動的スキーマのサポート

ツールは、ランタイムスキーマ処理に CallToolRequest を受け入れることができます。

@McpTool(name = "flexible-tool", description = "Process dynamic schema")
public CallToolResult processDynamic(CallToolRequest request) {
    Map<String, Object> args = request.arguments();

    // Process based on runtime schema
    String result = "Processed " + args.size() + " arguments dynamically";

    return CallToolResult.builder()
        .addTextContent(result)
        .build();
}

進捗状況の追跡

ツールは、長時間実行される操作を追跡するための進行状況トークンを受け取ることができます。

@McpTool(name = "long-task", description = "Long-running task with progress")
public String performLongTask(
        McpSyncRequestContext context,
        @McpToolParam(description = "Task name", required = true) String taskName) {

    // Access progress token from context
    String progressToken = context.request().progressToken();

    if (progressToken != null) {
        context.progress(p -> p.progress(0.0).total(1.0).message("Starting task"));

        // Perform work...

        context.progress(p -> p.progress(1.0).total(1.0).message("Task completed"));
    }

    return "Task " + taskName + " completed";
}

@McpResource

@McpResource アノテーションは、URI テンプレートを介してリソースへのアクセスを提供します。

基本的な使い方

@Component
public class ResourceProvider {

    @McpResource(
        uri = "config://{key}",
        name = "Configuration",
        description = "Provides configuration data")
    public String getConfig(String key) {
        return configData.get(key);
    }
}

ReadResourceResult 付

@McpResource(
    uri = "user-profile://{username}",
    name = "User Profile",
    description = "Provides user profile information")
public ReadResourceResult getUserProfile(String username) {
    String profileData = loadUserProfile(username);

    return new ReadResourceResult(List.of(
        new TextResourceContents(
            "user-profile://" + username,
            "application/json",
            profileData)
    ));
}

リクエストコンテキストを使用する場合

@McpResource(
    uri = "data://{id}",
    name = "Data Resource",
    description = "Resource with request context")
public ReadResourceResult getData(
        McpSyncRequestContext context,
        String id) {

    // Send logging notification using convenient method
    context.info("Accessing resource: " + id);

    // Ping the client
    context.ping();

    String data = fetchData(id);

    return new ReadResourceResult(List.of(
        new TextResourceContents("data://" + id, "text/plain", data)
    ));
}

@McpPrompt

@McpPrompt アノテーションは、AI 対話用のプロンプトメッセージを生成します。

基本的な使い方

@Component
public class PromptProvider {

    @McpPrompt(
        name = "greeting",
        description = "Generate a greeting message")
    public GetPromptResult greeting(
            @McpArg(name = "name", description = "User's name", required = true)
            String name) {

        String message = "Hello, " + name + "! How can I help you today?";

        return new GetPromptResult(
            "Greeting",
            List.of(new PromptMessage(Role.ASSISTANT, new TextContent(message)))
        );
    }
}

オプション引数を使用する場合

@McpPrompt(
    name = "personalized-message",
    description = "Generate a personalized message")
public GetPromptResult personalizedMessage(
        @McpArg(name = "name", required = true) String name,
        @McpArg(name = "age", required = false) Integer age,
        @McpArg(name = "interests", required = false) String interests) {

    StringBuilder message = new StringBuilder();
    message.append("Hello, ").append(name).append("!\n\n");

    if (age != null) {
        message.append("At ").append(age).append(" years old, ");
        // Add age-specific content
    }

    if (interests != null && !interests.isEmpty()) {
        message.append("Your interest in ").append(interests);
        // Add interest-specific content
    }

    return new GetPromptResult(
        "Personalized Message",
        List.of(new PromptMessage(Role.ASSISTANT, new TextContent(message.toString())))
    );
}

@McpComplete

@McpComplete アノテーションは、プロンプトの自動補完機能を提供します。

基本的な使い方

@Component
public class CompletionProvider {

    @McpComplete(prompt = "city-search")
    public List<String> completeCityName(String prefix) {
        return cities.stream()
            .filter(city -> city.toLowerCase().startsWith(prefix.toLowerCase()))
            .limit(10)
            .toList();
    }
}

CompleteRequest.CompleteArgument を使用する場合

@McpComplete(prompt = "travel-planner")
public List<String> completeTravelDestination(CompleteRequest.CompleteArgument argument) {
    String prefix = argument.value().toLowerCase();
    String argumentName = argument.name();

    // Different completions based on argument name
    if ("city".equals(argumentName)) {
        return completeCities(prefix);
    } else if ("country".equals(argumentName)) {
        return completeCountries(prefix);
    }

    return List.of();
}

CompleteResult 付

@McpComplete(prompt = "code-completion")
public CompleteResult completeCode(String prefix) {
    List<String> completions = generateCodeCompletions(prefix);

    return new CompleteResult(
        new CompleteResult.CompleteCompletion(
            completions,
            completions.size(),  // total
            hasMoreCompletions   // hasMore flag
        )
    );
}

ステートレス実装とステートフル実装

ステートフル操作とステートレス操作の両方で機能する統合インターフェースには、McpSyncRequestContext または McpAsyncRequestContext を使用します。

public record UserInfo(String name, String email, int age) {}

@McpTool(name = "unified-tool", description = "Tool with unified request context")
public String unifiedTool(
        McpSyncRequestContext context,
        @McpToolParam(description = "Input", required = true) String input) {

    // Access request and metadata
    String progressToken = context.request().progressToken();

    // Logging with convenient methods
    context.info("Processing: " + input);

    // Progress notifications (Note client should set a progress token
    // with its request to be able to receive progress updates)
    context.progress(50); // Simple percentage

    // Ping client
    context.ping();

    // Check capabilities before using
    if (context.elicitEnabled()) {
        // Request user input (only in stateful mode)
        StructuredElicitResult<UserInfo> elicitResult = context.elicit(UserInfo.class);
        if (elicitResult.action() == ElicitResult.Action.ACCEPT) {
            // Use elicited data
        }
    }

    if (context.sampleEnabled()) {
        // Request LLM sampling (only in stateful mode)
        CreateMessageResult samplingResult = context.sample("Generate response");
        // Use sampling result
    }

    return "Processed with unified context";
}

簡単な操作 (コンテキストなし)

単純な操作の場合は、コンテキストパラメーターを完全に省略できます。

@McpTool(name = "simple-add", description = "Simple addition")
public int simpleAdd(
        @McpToolParam(description = "First number", required = true) int a,
        @McpToolParam(description = "Second number", required = true) int b) {
    return a + b;
}

軽量ステートレス (McpTransportContext を使用する場合)

最小限のトランスポートコンテキストが必要なステートレス操作の場合:

@McpTool(name = "stateless-tool", description = "Stateless with transport context")
public String statelessTool(
        McpTransportContext context,
        @McpToolParam(description = "Input", required = true) String input) {
    // Access transport-level context only
    // No bidirectional operations (roots, elicitation, sampling)
    return "Processed: " + input;
}
ステートレスサーバーは双方向操作をサポートしていません。

ステートレスモードで McpSyncRequestContext または McpAsyncRequestContext を使用するメソッドは無視されます。

サーバー型によるメソッドフィルタリング

MCP アノテーションフレームワークは、サーバーの種類とメソッドの特性に基づいて、アノテーション付きメソッドを自動的にフィルタリングします。これにより、各サーバー構成に適したメソッドのみが登録されます。フィルタリングされたメソッドごとに警告がログに記録されるため、デバッグに役立ちます。

同期フィルタリングと非同期フィルタリング

同期サーバー

同期サーバー (spring.ai.mcp.server.type=SYNC で構成) は、次の機能を備えた同期プロバイダーを使用します。

  • 非リアクティブ型戻り値を持つ受諾メソッド:

    • 基本タイプ (intdoubleboolean)

    • オブジェクトタイプ (StringInteger、カスタム POJO)

    • MCP 型 (CallToolResultReadResourceResultGetPromptResultCompleteResult)

    • コレクション (List<String>Map<String, Object>)

  • リアクティブな戻り値の型を持つフィルターに掛けるメソッド:

    • Mono<T>

    • Flux<T>

    • Publisher<T>

@Component
public class SyncTools {

    @McpTool(name = "sync-tool", description = "Synchronous tool")
    public String syncTool(String input) {
        // This method WILL be registered on sync servers
        return "Processed: " + input;
    }

    @McpTool(name = "async-tool", description = "Async tool")
    public Mono<String> asyncTool(String input) {
        // This method will be FILTERED OUT on sync servers
        // A warning will be logged
        return Mono.just("Processed: " + input);
    }
}

非同期サーバー

非同期サーバー (spring.ai.mcp.server.type=ASYNC で構成) は、次の非同期プロバイダーを使用します。

  • リアクティブな戻り値の型を持つ受諾メソッド:

    • Mono<T> (単一の結果の場合)

    • Flux<T> (ストリーミング結果用)

    • Publisher<T> (汎用リアクティブ型)

  • 非リアクティブ型戻り値を持つフィルターに掛けるメソッド:

    • 基本タイプ

    • オブジェクトタイプ

    • コレクション

    • MCP 結果型

@Component
public class AsyncTools {

    @McpTool(name = "async-tool", description = "Async tool")
    public Mono<String> asyncTool(String input) {
        // This method WILL be registered on async servers
        return Mono.just("Processed: " + input);
    }

    @McpTool(name = "sync-tool", description = "Sync tool")
    public String syncTool(String input) {
        // This method will be FILTERED OUT on async servers
        // A warning will be logged
        return "Processed: " + input;
    }
}

ステートフルフィルタリングとステートレスフィルタリング

ステートフルサーバー

ステートフルサーバーは双方向通信をサポートし、次のメソッドを受け入れます。

  • 双方向コンテキストパラメーター :

    • McpSyncRequestContext (同期操作用)

    • McpAsyncRequestContext (非同期操作の場合)

    • McpSyncServerExchange (レガシー、同期操作用)

    • McpAsyncServerExchange (レガシー、非同期操作用)

  • 双方向操作のサポート:

    • roots() - ルートディレクトリにアクセスする

    • elicit() - ユーザー入力をリクエストする

    • sample() - LLM サンプリングのリクエスト

@Component
public class StatefulTools {

    @McpTool(name = "interactive-tool", description = "Tool with bidirectional operations")
    public String interactiveTool(
            McpSyncRequestContext context,
            @McpToolParam(description = "Input", required = true) String input) {

        // This method WILL be registered on stateful servers
        // Can use elicitation, sampling, roots
        if (context.sampleEnabled()) {
            var samplingResult = context.sample("Generate response");
            // Process sampling result...
        }

        return "Processed with context";
    }
}

ステートレスサーバー

ステートレスサーバーは、単純なリクエストとレスポンスのパターンに最適化されており、次の特徴があります。

  • 双方向コンテキストパラメーターを持つフィルターに掛けるメソッド:

    • McpSyncRequestContext のメソッドはスキップされます

    • McpAsyncRequestContext のメソッドはスキップされます

    • McpSyncServerExchange のメソッドはスキップされます

    • McpAsyncServerExchange のメソッドはスキップされます

    • フィルタリングされたメソッドごとに警告が記録されます

  • 受諾メソッド:

    • McpTransportContext (軽量ステートレスコンテキスト)

    • コンテキストパラメーターが全くない

    • 通常の @McpToolParam パラメーターのみ

  • 双方向操作をサポートしません:

    • roots() - 使用不可

    • elicit() - 使用不可

    • sample() - 使用不可

@Component
public class StatelessTools {

    @McpTool(name = "simple-tool", description = "Simple stateless tool")
    public String simpleTool(@McpToolParam(description = "Input") String input) {
        // This method WILL be registered on stateless servers
        return "Processed: " + input;
    }

    @McpTool(name = "context-tool", description = "Tool with transport context")
    public String contextTool(
            McpTransportContext context,
            @McpToolParam(description = "Input") String input) {
        // This method WILL be registered on stateless servers
        return "Processed: " + input;
    }

    @McpTool(name = "bidirectional-tool", description = "Tool with bidirectional context")
    public String bidirectionalTool(
            McpSyncRequestContext context,
            @McpToolParam(description = "Input") String input) {
        // This method will be FILTERED OUT on stateless servers
        // A warning will be logged
        return "Processed with sampling";
    }
}

フィルタリングの概要

サーバータイプ 受け入れられる方法 フィルタリングされたメソッド

ステートフル同期

非リアクティブリターン + 双方向のコンテキスト

リアクティブリターン (Mono/Flux)

非同期ステートフル

リアクティブリターン(Mono/Flux)+ 双方向コンテキスト

非リアクティブ return

ステートレス同期

非リアクティブリターン + 双方向のコンテキストなし

リアクティブリターンまたは双方向コンテキストパラメーター

非同期ステートレス

リアクティブリターン(Mono/Flux)+ 双方向コンテキストなし

非リアクティブ戻り値または双方向のコンテキストパラメーター

メソッドフィルタリングのベストプラクティス:
  1. 方法を統一するをサーバーの種類に置き換えます。同期サーバーの場合は同期メソッドを使用し、非同期サーバーの場合は非同期を使用します。

  2. わかりやすくするためにステートフルとステートレスを分けるの実装を異なるクラスに分割する

  3. フィルタリングされたメソッドの警告の起動時のログを確認する

  4. 適切なコンテキストを使用する - ステートフルの場合は McpSyncRequestContext/McpAsyncRequestContext、ステートレスの場合は McpTransportContext 

  5. ステートフルとステートレスの両方をサポートする場合は両方のモードをテストする(デプロイ)

非同期サポート

すべてのサーバーアノテーションは、Reactor を使用した非同期実装をサポートします。

@Component
public class AsyncTools {

    @McpTool(name = "async-fetch", description = "Fetch data asynchronously")
    public Mono<String> asyncFetch(
            @McpToolParam(description = "URL", required = true) String url) {

        return Mono.fromCallable(() -> {
            // Simulate async operation
            return fetchFromUrl(url);
        }).subscribeOn(Schedulers.boundedElastic());
    }

    @McpResource(uri = "async-data://{id}", name = "Async Data")
    public Mono<ReadResourceResult> asyncResource(String id) {
        return Mono.fromCallable(() -> {
            String data = loadData(id);
            return new ReadResourceResult(List.of(
                new TextResourceContents("async-data://" + id, "text/plain", data)
            ));
        }).delayElements(Duration.ofMillis(100));
    }
}

Spring Boot 統合

Spring Boot 自動構成により、アノテーション付き Bean が自動的に検出され、登録されます。

@SpringBootApplication
public class McpServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(McpServerApplication.class, args);
    }
}

@Component
public class MyMcpTools {
    // Your @McpTool annotated methods
}

@Component
public class MyMcpResources {
    // Your @McpResource annotated methods
}

自動構成は次のようになります。

  1. MCP アノテーション付きの Bean をスキャンする

  2. 適切な仕様を作成する

  3. MCP サーバーに登録する

  4. 構成に基づいて同期と非同期の両方の実装を処理する

プロパティの構成

サーバーアノテーションスキャナーを構成します。

spring:
  ai:
    mcp:
      server:
        type: SYNC  # or ASYNC
        annotation-scanner:
          enabled: true