核心组件
MCP(模型上下文协议)定义了三种核心组件类型,本 SDK 通过注解简化了这些组件的创建过程。
资源组件用于向 LLM 暴露数据,类似于 Web API 中的 GET 请求。
import com.github.thought2code.mcp.annotated.annotation.McpResource;
public class MyResources { @McpResource(uri = "system://info", description = "System information") public Map<String, String> getSystemInfo() { Map<String, String> info = new HashMap<>(); info.put("os", System.getProperty("os.name")); info.put("java", System.getProperty("java.version")); info.put("cores", String.valueOf(Runtime.getRuntime().availableProcessors())); return info; }}| 参数 | 说明 | 是否必填 |
|---|---|---|
uri |
资源的唯一标识符(URI 格式) | 是 |
description |
帮助 LLM 理解资源的说明 | 否(默认依次使用 name、方法名) |
name |
资源名称 | 否(默认为方法名) |
title |
资源标题 | 否(默认为 name) |
mimeType |
资源内容的 MIME 类型 | 否(默认为 text/plain) |
roles |
允许访问资源的角色 | 否(默认为 ASSISTANT、USER) |
priority |
资源优先级 | 否(默认为 1.0) |
工具组件用于执行操作或计算,类似于 Web API 中的 POST 请求。
import com.github.thought2code.mcp.annotated.annotation.McpTool;import com.github.thought2code.mcp.annotated.annotation.McpToolParam;
public class MyTools { @McpTool(description = "Calculate the sum of two numbers") public int add( @McpToolParam(name = "a", description = "First number") int a, @McpToolParam(name = "b", description = "Second number") int b ) { return a + b; }
@McpTool(description = "Read complete file contents with UTF-8 encoding") public String readFile( @McpToolParam(name = "path", description = "File path") String path ) { try { return Files.readString(Path.of(path)); } catch (IOException e) { return "Error reading file: " + e.getMessage(); } }}@McpTool
Section titled “@McpTool”| 参数 | 说明 | 是否必填 |
|---|---|---|
description |
帮助 LLM 理解工具的说明 | 否(默认依次使用工具 name、方法名) |
name |
工具名称 | 否(默认为方法名) |
title |
用于展示的工具标题 | 否 |
@McpToolParam
Section titled “@McpToolParam”| 参数 | 说明 | 是否必填 |
|---|---|---|
name |
参数名称 | 是 |
description |
参数说明 | 否(默认为 name) |
required |
参数是否必填 | 否(默认为 true) |
@McpTool:将方法标记为 MCP 工具。@McpToolParam:将方法参数标记为工具参数。name:参数名称。description:参数说明。required:参数是否必填(默认为true)。
提示词组件用于定义可复用的提示词模板。
import com.github.thought2code.mcp.annotated.annotation.McpPrompt;import com.github.thought2code.mcp.annotated.annotation.McpPromptParam;
public class MyPrompts { @McpPrompt(description = "Generate code for a given task") public String generateCode( @McpPromptParam(name = "language", description = "Programming language") String language, @McpPromptParam(name = "task", description = "Task description") String task ) { return String.format("Write %s code to: %s", language, task); }
@McpPrompt(description = "Format text as specified style") public String formatText( @McpPromptParam(name = "text", description = "Text to format") String text, @McpPromptParam(name = "style", description = "Format style (e.g., formal, casual, technical)") String style ) { return String.format("Rewrite the following text in a %s style: %s", style, text); }}@McpPrompt
Section titled “@McpPrompt”| 参数 | 说明 | 是否必填 |
|---|---|---|
description |
帮助 LLM 理解提示词的说明 | 否(默认依次使用提示词 name、方法名) |
name |
提示词名称 | 否(默认为方法名) |
title |
用于展示的提示词标题 | 否 |
@McpPromptParam
Section titled “@McpPromptParam”| 参数 | 说明 | 是否必填 |
|---|---|---|
name |
参数名称 | 是 |
description |
参数说明 | 否(默认为 name) |
required |
参数是否必填 | 否(默认为 true) |
补全功能为资源 URI 和提示词参数提供自动补全建议。
处理方法必须返回 CompletionResult,并且只能接收一个 McpSchema.CompleteRequest.CompleteArgument 类型的参数。该参数表示正在补全的参数,其 name() 和 value() 来自 MCP 请求。
@McpResourceCompletion.uri 必须与 @McpResource 上的 uri 完全一致,包括 file://{path} 这样的 URI 模板。
补全处理方法应与使用相同 URI 模式的资源配对:
import com.github.thought2code.mcp.annotated.annotation.McpResource;import com.github.thought2code.mcp.annotated.annotation.McpResourceCompletion;import com.github.thought2code.mcp.annotated.server.component.completion.CompletionResult;import io.modelcontextprotocol.spec.McpSchema;
import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.util.List;import java.util.stream.Collectors;
public class MyFileResources { @McpResource(uri = "file://{path}", description = "Read a file by path") public String readFile() { return "file content"; }
@McpResourceCompletion(uri = "file://{path}") public CompletionResult completeFileUri(McpSchema.CompleteRequest.CompleteArgument argument) { String prefix = argument.value() != null ? argument.value() : ""; try { List<String> paths = Files.list(Paths.get(prefix.isEmpty() ? "." : prefix)) .map(Path::toString) .limit(50) .collect(Collectors.toList()); return CompletionResult.builder() .values(paths) .total(paths.size()) .hasMore(false) .build(); } catch (Exception e) { return CompletionResult.empty(); } }}@McpPromptCompletion.name 必须与已注册的提示词名称一致:如果设置了 @McpPrompt.name 属性则使用该属性,否则使用 @McpPrompt 方法名。当一个提示词有多个参数时,应通过 argument.name() 进行筛选;该名称必须与正在补全的 @McpPromptParam.name 一致。
import com.github.thought2code.mcp.annotated.annotation.McpPromptCompletion;import com.github.thought2code.mcp.annotated.server.component.completion.CompletionResult;import io.modelcontextprotocol.spec.McpSchema;
import java.util.List;
public class MyPromptCompletions { @McpPromptCompletion(name = "generateCode") public CompletionResult completeGenerateCode(McpSchema.CompleteRequest.CompleteArgument argument) { if (!"language".equals(argument.name())) { return CompletionResult.empty(); } return CompletionResult.builder() .values(List.of("Java", "Python", "JavaScript", "Go", "Rust")) .total(5) .hasMore(false) .build(); }}定义 MCP 组件后,它们会自动注册到服务器。只需确保组件类位于服务器应用的注册范围内。
每个组件类一个实例
Section titled “每个组件类一个实例”SDK 会为每个组件类创建一个对象(通过其公共无参构造方法),并使用同一实例调用该类上的所有注解方法。并发请求会共享一个对象,因此:
- 组件类必须提供可访问的无参构造方法。
- 尽可能让组件类保持无状态。
- 所有可变实例字段都必须是线程安全的,或者由你同步访问。
- 不要将实例字段作为请求级存储。
SYNC 与 ASYNC 服务器类型
Section titled “SYNC 与 ASYNC 服务器类型”在 mcp-server.yml 中设置 type: ASYNC 会使用底层 SDK 的异步 MCP 服务器 API。处理器通过 Mono.fromCallable(...) 包装阻塞方法——ASYNC 模式不等于 Project Reactor 编程模型。@McpTool、@McpPrompt 和 @McpResource 方法仍然是普通的同步 Java 代码。
如需指定特定的包路径,可以使用以下方式:
@McpServerApplication(basePackageClass = MyMcpServer.class)// 或者@McpServerApplication(basePackage = "com.example.mcp.components")当主类同时提供多个选项时,按以下顺序解析:
basePackageClass(不为Object.class时)——使用该类所在的包。- 非空白的
basePackage。 - 传递给
McpApplication.run()的类所在的包。
最终包下的子包也会包含在扫描范围内,其他包中的类会被忽略。
工具可以通过返回实现了 McpStructuredContent 的类型来提供结构化内容,以生成更丰富的响应。该类型通常可以是一个 record,并在字段上使用 @McpJsonSchemaProperty。API 中不存在 McpStructuredContent.of(...) 辅助方法。
import com.github.thought2code.mcp.annotated.annotation.McpJsonSchemaDefinition;import com.github.thought2code.mcp.annotated.annotation.McpJsonSchemaProperty;import com.github.thought2code.mcp.annotated.annotation.McpTool;import com.github.thought2code.mcp.annotated.annotation.McpToolParam;import com.github.thought2code.mcp.annotated.server.McpStructuredContent;
public class UserTools {
@McpJsonSchemaDefinition public record User( @McpJsonSchemaProperty(description = "User id") String id, @McpJsonSchemaProperty(description = "Display name") String name) implements McpStructuredContent {
@Override public String asTextContent() { return "User " + id + ": " + name; } }
@McpTool(description = "Get user details") public User getUser(@McpToolParam(name = "id", description = "User ID") String id) { return new User(id, "Ada"); }}如果工具方法抛出任何异常,服务器会返回 isError 为 true 的 CallToolResult 和通用的方法调用错误信息。目前不会将异常消息转发给客户端。
对于校验失败等预期错误,请返回普通值(例如 String),这样工具调用仍会得到 isError 为 false 的成功结果:
@McpTool(description = "Divide two numbers")public String divide( @McpToolParam(name = "a", description = "Dividend") double a, @McpToolParam(name = "b", description = "Divisor") double b) { if (b == 0) { return "Cannot divide by zero."; } return Double.toString(a / b);}