连接生命周期
MCP为客户端-服务端连接定义了严格的生命周期,以确保适当的能力协商和状态管理。通信流程包括:
- 初始化:能力协商和协议版本协议。
- 操作:正常协议通信。
- 关闭:连接的优雅终止。
初始化
初始化阶段必须是客户端和服务器之间的第一次交互,在初始化完成前,服务端不会处理任何其他消息。在此阶段,客户端和服务器:确认协议版本兼容性,交换和协商能力,共享实现细节。
客户端必须通过发送包含以下内容的初始化请求来启动此阶段:支持的协议版本,客户端能力,客户端实现信息。JSON-RPC消息示例如下:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {
"roots": {
"listChanged": true
},
"sampling": {
}
},
"clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
}
}
}
服务器必须用自己的能力和信息进行响应,JSON-RPC消息示例如下:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"logging": {},
"prompts": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"tools": {
"listChanged": true
}
},
"serverInfo": {
"name": "ExampleServer",
"version": "1.0.0"
},
"instructions": "Optional instructions for the client"
}
}
初始化成功后,客户端必须发送一个初始化的通知,表明它已经准备好开始正常的操作,JSON-RPC消息格式如下:
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
操作
在操作阶段,客户端和服务器根据协商的功能交换消息。双方应:
- 尊重协商的协议版本。
- 只使用成功协商的能力。
Tool
- 获取工具列表。
# 客户端请求
{"jsonrpc":"2.0","method":"tools/list","id":2}
# 服务端响应
{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"getAllBooks","description":"获取所有的书","inputSchema":{"type":"object","properties":{}}},{"name":"getBookByYear","description":"根据年份获取对应年份的书","inputSchema":{"type":"object","properties":{"year":{"type":"number"}},"required":["year"]}}]}}
- 工具调用
# 客户端请求
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"getAllBooks","arguments":{}},"id":3}
# 服务端响应
{"jsonrpc":"2.0","id":3,"result":{"content":\[{"type":"text","text":"Book\[title=Java怎么学, url=<https://www.baidu.com>, year=2025]"},{"type":"text","text":"Book\[title=程序员怎么养生, url=<https://www.baidu.com>, year=2024]"},{"type":"text","text":"Book\[title=ai的发展, url=<https://www.baidu.com>, year=2001]"},{"type":"text","text":"Book\[title=时间简史, url=<https://www.baidu.com>, year=2008]"}],"isError":false}
Resource
- 获取资源列表
# 客户端请求
{"jsonrpc":"2.0","method":"resources/list","id":2}
# 服务端响应
{"jsonrpc":"2.0","id":2,"result":{"resources":\[{"uri":"custom://resource","name":"示例资源","description":"这是一个示例资源","mimeType":"text/plain"}]}}
- 读取资源
# 客户端请求
{"jsonrpc":"2.0","method":"resources/read","params":{"uri":"custom://resource"},"id":3}
# 服务端响应
{"jsonrpc":"2.0","id":3,"result":{"contents":\[{"uri":"custom://resource/content","mimeType":"text/plain","text":"这是资源内容示例"}]}}
Prompt
- 获取提示词列表
# 客户端请求
{"jsonrpc":"2.0","method":"prompts/list","id":2}
# 服务端响应
{"jsonrpc":"2.0","id":2,"result":{"prompts":\[{"name":"greeting","description":"生成问候语","arguments":\[{"name":"name","description":"用户名称","required":true}]}]}}
- 获取提示词(很诡异,SDK的提示词中居然没有System的)
# 客户端请求
{"jsonrpc":"2.0","method":"prompts/get","params":{"name":"greeting","arguments":{"name":"Tom"}},"id":3}
# 服务端响应
{"jsonrpc":"2.0","id":3,"result":{"description":"为用户Tom生成的问候语","messages":\[{"role":"assistant","content":{"type":"text","text":"你是一个友好的助手,请为用户生成问候语"}},{"role":"user","content":{"type":"text","text":"你好,请给我一个友好的问候"}},{"role":"assistant","content":{"type":"text","text":"你好,Tom!很高兴见到你。今天过得怎么样?"}}]}}
关闭
在关闭阶段,一方(通常是客户端)干净地终止协议连接,并没有定义特定的关闭消息,应该使用底层传输机制来表示连接终止。
服务端
POM配置
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>0.9.0</version>
</dependency>
<!-- Used by the HttpServletSseServerTransport -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>11.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<version>11.0.2</version>
</dependency>
代码实现
MCP资源
- Tool
public class BookToolSpec {
public static McpServerFeatures.SyncToolSpecification getAllBookSpec() {
var schema = """
{
"type" : "object",
"properties" : {
}
}
""";
return new McpServerFeatures.SyncToolSpecification(new McpSchema.Tool("getAllBooks", "获取所有的书", schema),
(exchange, arguments) -> {
// 工具的实现
List<Book> bookList = new BookTool().getBooks();
List<McpSchema.Content> result = bookList.stream()
.map(p -> new McpSchema.TextContent(p.toString()))
.collect(Collectors.toUnmodifiableList());
return new McpSchema.CallToolResult(result, false);
});
}
public static McpServerFeatures.SyncToolSpecification getBookByYearSpec() {
var schema = """
{
"type" : "object",
"properties" : {
"year" : {
"type" : "number"
}
},
"required" : ["year"]
}
""";
return new McpServerFeatures.SyncToolSpecification(new McpSchema.Tool("getBookByYear", "根据年份获取对应年份的书", schema),
(exchange, arguments) -> {
// 工具的实现
Integer year = (Integer) arguments.get("year");
List<Book> bookList = new BookTool().getBookListByYear(year);
List<McpSchema.Content> result = bookList.stream()
.map(p -> new McpSchema.TextContent(p.toString()))
.collect(Collectors.toUnmodifiableList());
return new McpSchema.CallToolResult(result, false);
});
}
public static class BookTool {
private final List<Book> books = new ArrayList<>();
public BookTool() {
var one = new Book("Java怎么学", "https://www.baidu.com", 2025);
var two = new Book("程序员怎么养生", "https://www.baidu.com", 2024);
var three = new Book("ai的发展", "https://www.baidu.com", 2001);
var four = new Book("时间简史", "https://www.baidu.com", 2008);
this.books.addAll(List.of(one, two, three, four));
}
public List<Book> getBooks() {
return books;
}
public List<Book> getBookListByYear(int year) {
return books.stream().filter(p -> p.year() == year).collect(Collectors.toList());
}
}
public record Book(String title, String url, int year) {
}
}
- Resource
public class ExampleResourceSpec {
public static McpServerFeatures.SyncResourceSpecification getExampleResourceSpec() {
return new McpServerFeatures.SyncResourceSpecification(
new McpSchema.Resource("custom://resource", "示例资源", "这是一个示例资源", "text/plain", null),
(exchange, request) -> {
List<McpSchema.ResourceContents> contents = new ArrayList<>();
String content = "这是资源内容示例";
contents
.add(new McpSchema.TextResourceContents("custom://resource/content", "text/plain", content));
return new McpSchema.ReadResourceResult(contents);
});
}
}
- Prompt
public class GreetingPromptSpec {
public static McpServerFeatures.SyncPromptSpecification getGreetingPromptSpec() {
return new McpServerFeatures.SyncPromptSpecification(
new McpSchema.Prompt("greeting", "生成问候语", List.of(new McpSchema.PromptArgument("name", "用户名称", true))),
(exchange, request) -> {
List<McpSchema.PromptMessage> messages = new ArrayList<>();
String name;
name = new String(((String) request.arguments().get("name")).getBytes(StandardCharsets.UTF_8),
StandardCharsets.UTF_8);
if (name.isEmpty()) {
name = "访客";
}
// 创建对话消息序列
McpSchema.PromptMessage userMessage = new McpSchema.PromptMessage(McpSchema.Role.USER,
new McpSchema.TextContent("你好,请给我一个友好的问候"));
McpSchema.PromptMessage systemMessage = new McpSchema.PromptMessage(McpSchema.Role.ASSISTANT,
new McpSchema.TextContent("你是一个友好的助手,请为用户生成问候语"));
McpSchema.PromptMessage assistantMessage = new McpSchema.PromptMessage(McpSchema.Role.ASSISTANT,
new McpSchema.TextContent("你好," + name + "!很高兴见到你。今天过得怎么样?"));
messages.add(systemMessage);
messages.add(userMessage);
messages.add(assistantMessage);
return new McpSchema.GetPromptResult("为用户" + name + "生成的问候语", messages);
});
}
}
MCP通信
- MCPTransportType
public enum MCPTransportType {
stdio,
sse
}
- MCPTransportFactory
public class MCPTransportFactory {
public static McpServerTransportProvider createTransportProvider(MCPTransportType type) {
return switch (type) {
case stdio -> createStdioTransportProvider();
case sse -> createSseTransportProvider();
default -> throw new IllegalArgumentException("Invalid transport type: " + type);
};
}
private static McpServerTransportProvider createStdioTransportProvider() {
return new StdioServerTransportProvider(new ObjectMapper());
}
private static McpServerTransportProvider createSseTransportProvider() {
Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
String baseDir = System.getProperty("java.io.tmpdir");
tomcat.setBaseDir(baseDir);
Context context = tomcat.addContext("", baseDir);
HttpServletSseServerTransportProvider
transportProvider = new HttpServletSseServerTransportProvider(new ObjectMapper(), "/message");
Tomcat.addServlet(context, "transportProvider", transportProvider);
context.addServletMappingDecoded("/sse", "transportProvider");
context.addServletMappingDecoded("/message", "transportProvider");
try {
tomcat.start();
tomcat.getConnector();
} catch (LifecycleException e) {
throw new RuntimeException("Failed to start Tomcat", e);
}
return transportProvider;
}
}
MCP Server
public class MCPServer {
public static void main(String[] args) {
McpServerTransportProvider transportProvider = MCPTransportFactory.createTransportProvider(MCPTransportType.sse);
// 配置mcp-server信息
McpSyncServer server = McpServer.sync(transportProvider)
.serverInfo("custom-mcp-server", "1.0.0") // 设置服务器标识
.capabilities(McpSchema.ServerCapabilities.builder()
.tools(true) // 启用工具功能
.resources(true, true) // 启用资源读写功能
.prompts(true) // 启用提示功能
.logging() // 启用日志功能
.build())
.build();
// 添加工具
server.addTool(BookToolSpec.getAllBookSpec());
server.addTool(BookToolSpec.getBookByYearSpec());
// 添加资源
server.addResource(ExampleResourceSpec.getExampleResourceSpec());
// 添加提示词
server.addPrompt(GreetingPromptSpec.getGreetingPromptSpec());
// 发送日志通知
server.loggingNotification(McpSchema.LoggingMessageNotification.builder()
.level(McpSchema.LoggingLevel.INFO)
.logger("custom-logger")
.data("Custom MCP server initialized")
.build());
// 关闭服务器
// syncServer.close();
}
}
测试验证
stdio
- 服务启动控制台输出
{"jsonrpc":"2.0","method":"notifications/tools/list_changed"}
{"jsonrpc":"2.0","method":"notifications/tools/list_changed"}
{"jsonrpc":"2.0","method":"notifications/resources/list_changed"}
{"jsonrpc":"2.0","method":"notifications/prompts/list_changed"}
{"jsonrpc":"2.0","method":"notifications/message","params":{"level":"info","logger":"custom-logger","data":"Custom MCP server initialized"}}
- 连接初始化
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{"roots":{"listChanged":true},"sampling":{}},"clientInfo":{"name":"ExampleClient","version":"1.0.0"}}}
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"logging":{},"prompts":{"listChanged":true},"resources":{"subscribe":true,"listChanged":true},"tools":{"listChanged":true}},"serverInfo":{"name":"custom-mcp-server","version":"1.0.0"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
-
Tool使用
-
- 获取工具列表
{"jsonrpc":"2.0","method":"tools/list","id":2}
{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"getAllBooks","description":"获取所有的书","inputSchema":{"type":"object","properties":{}}},{"name":"getBookByYear","description":"根据年份获取对应年份的书","inputSchema":{"type":"object","properties":{"year":{"type":"number"}},"required":["year"]}}]}}
-
- 工具调用
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"getAllBooks","arguments":{}},"id":3} {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"Book[title=Java怎么学, url=https://www.baidu.com, year=2025]"},{"type":"text","text":"Book[title=程序员怎么养生, url=https://www.baidu.com, year=2024]"},{"type":"text","text":"Book[title=ai的发展, url=https://www.baidu.com, year=2001]"},{"type":"text","text":"Book[title=时间简史, url=https://www.baidu.com, year=2008]"}],"isError":false}}
sse
- 服务启动控制台输出
- 连接初始化
-
- 建立连接
$ curl 'http://127.0.0.1:8080/sse' event: endpoint data: /message?sessionId=b5c9ba22-fb64-45ca-acb7-8a3c40f86509
-
- 客户端与服务端协商
$ curl -X POST 'http://127.0.0.1:8080/message?sessionId=b5c9ba22-fb64-45ca-acb7-8a3c40f86509' -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{"roots":{"listChanged":true},"sampling":{}},"clientInfo":{"name":"ExampleClient","version":"1.0.0"}}}'
-
- 客户端确认
$ curl -X POST 'http://127.0.0.1:8080/message?sessionId=b5c9ba22-fb64-45ca-acb7-8a3c40f86509' -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
- Tool使用
-
- 获取工具列表
$ curl -X POST 'http://127.0.0.1:8080/message?sessionId=d8ffe4da-f2b9-400e-b548-7403869fb24e' -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"tools/list","id":2}'
-
- 工具调用
curl -X POST 'http://127.0.0.1:8080/message?sessionId=d8ffe4da-f2b9-400e-b548-7403869fb24e' -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"getAllBooks","arguments":{}},"id":3}'
streamableHttp
当前版本(0.9.0)的MCP SDK尚不支持streamableHttp,该种通信机制未验证。