跳转到内容

核心组件

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 允许访问资源的角色 否(默认为 ASSISTANTUSER
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();
}
}
}
参数 说明 是否必填
description 帮助 LLM 理解工具的说明 否(默认依次使用工具 name、方法名)
name 工具名称 否(默认为方法名)
title 用于展示的工具标题
参数 说明 是否必填
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);
}
}
参数 说明 是否必填
description 帮助 LLM 理解提示词的说明 否(默认依次使用提示词 name、方法名)
name 提示词名称 否(默认为方法名)
title 用于展示的提示词标题
参数 说明 是否必填
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 组件后,它们会自动注册到服务器。只需确保组件类位于服务器应用的注册范围内。

SDK 会为每个组件类创建一个对象(通过其公共无参构造方法),并使用同一实例调用该类上的所有注解方法。并发请求会共享一个对象,因此:

  • 组件类必须提供可访问的无参构造方法。
  • 尽可能让组件类保持无状态。
  • 所有可变实例字段都必须是线程安全的,或者由你同步访问。
  • 不要将实例字段作为请求级存储。

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")

当主类同时提供多个选项时,按以下顺序解析:

  1. basePackageClass(不为 Object.class 时)——使用该类所在的包。
  2. 非空白的 basePackage
  3. 传递给 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");
}
}

如果工具方法抛出任何异常,服务器会返回 isErrortrueCallToolResult 和通用的方法调用错误信息。目前不会将异常消息转发给客户端。

对于校验失败等预期错误,请返回普通值(例如 String),这样工具调用仍会得到 isErrorfalse 的成功结果:

@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);
}