このバージョンはまだ開発中であり、まだ安定しているとは考えられていません。最新のスナップショットバージョンについては、Spring AI 1.0.3 を使用してください。 |
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");
}リクエストコンテキストを使用する場合
Tools can access the request context for advanced operations:
@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)))
);
}
}With Optional Arguments
@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
The @McpComplete annotation provides auto-completion functionality for prompts.
基本的な使い方
@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();
}
}With 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
)
);
}Stateless vs Stateful Implementations
Unified Request Context (推奨)
Use McpSyncRequestContext or McpAsyncRequestContext for a unified interface that works with both stateful and stateless operations:
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";
}Simple Operations (コンテキストなし)
For simple operations, you can omit context parameters entirely:
@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;
}Lightweight Stateless (with McpTransportContext)
For stateless operations where you need minimal transport context:
@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;
}| Stateless servers do not support bidirectional operations: |
Therefore methods using McpSyncRequestContext or McpAsyncRequestContext in stateless mode are ignored.
Method Filtering by Server Type
The MCP annotations framework automatically filters annotated methods based on the server type and method characteristics. This ensures that only appropriate methods are registered for each server configuration. A warning is logged for each filtered method to help with debugging.
Synchronous vs Asynchronous Filtering
Synchronous Servers
Synchronous servers (configured with spring.ai.mcp.server.type=SYNC) use synchronous providers that:
受諾 methods with non-reactive return types:
基本タイプ (
int、double、boolean)オブジェクトタイプ (
String,Integer, custom POJOs)MCP types (
CallToolResult、ReadResourceResult、GetPromptResult、CompleteResult)コレクション (
List<String>,Map<String, Object>)
フィルターに掛ける methods with reactive return types:
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);
}
}Asynchronous Servers
Asynchronous servers (configured with spring.ai.mcp.server.type=ASYNC) use asynchronous providers that:
受諾 methods with reactive return types:
Mono<T>(for single results)Flux<T>(for streaming results)Publisher<T>(generic reactive type)
フィルターに掛ける methods with non-reactive return types:
基本タイプ
オブジェクトタイプ
コレクション
MCP result types
@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;
}
}Stateful vs Stateless Filtering
Stateful Servers
Stateful servers support bidirectional communication and accept methods with:
Bidirectional context parameters :
McpSyncRequestContext(for sync operations)McpAsyncRequestContext(for async operations)McpSyncServerExchange(legacy, for sync operations)McpAsyncServerExchange(legacy, for async operations)
Support for bidirectional operations:
roots()- Access root directorieselicit()- Request user inputsample()- Request LLM sampling
@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";
}
}Stateless Servers
Stateless servers are optimized for simple request-response patterns and:
フィルターに掛ける methods with bidirectional context parameters:
Methods with
McpSyncRequestContextare skippedMethods with
McpAsyncRequestContextare skippedMethods with
McpSyncServerExchangeare skippedMethods with
McpAsyncServerExchangeare skippedA warning is logged for each filtered method
受諾 methods with:
McpTransportContext(lightweight stateless context)No context parameter at all
Only regular
@McpToolParamparameters
Do not support bidirectional operations:
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";
}
}Filtering Summary
| サーバータイプ | Accepted Methods | Filtered Methods |
|---|---|---|
Sync Stateful | Non-reactive returns + bidirectional context | Reactive returns (Mono/Flux) |
Async Stateful | Reactive returns (Mono/Flux) + bidirectional context | Non-reactive returns |
Sync Stateless | Non-reactive returns + no bidirectional context | Reactive returns OR bidirectional context parameters |
Async Stateless | Reactive returns (Mono/Flux) + no bidirectional context | Non-reactive returns OR bidirectional context parameters |
| Best Practices for Method Filtering: |
Keep methods aligned with your server type - use sync methods for sync servers, async for async servers
Separate stateful and stateless implementations into different classes for clarity
Check logs during startup for filtered method warnings
Use the right context -
McpSyncRequestContext/McpAsyncRequestContextfor stateful,McpTransportContextfor statelessTest both modes if you support both stateful and stateless deployments
Async Support
All server annotations support asynchronous implementations using 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 統合
With Spring Boot auto-configuration, annotated beans are automatically detected and registered:
@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
}The auto-configuration will:
Scan for beans with MCP annotations
Create appropriate specifications
Register them with the MCP server
Handle both sync and async implementations based on configuration