在上一篇文章中介绍了 Java SDK Client 源码,并实现一个 Mcp Client 调用现成的 @modelcontextprotocol/server-filesystem,本文将实现一个 Mcp Server。
什么 Mcp Server
Mcp Server 是一个轻量级程序,每个程序都通过标准化的 Model Context Protocol 公开特定功能。可以向Mcp Client 三项核心能力 tools、resources 以及 templates。
Mcp Server 三项核心能力
| 能力 | 说明 |
|---|---|
| tools | 工具,model-controlled,由模型决策使用哪个工具 |
| resources | 资源,application-controlled,应用角色使用 |
| templates | 模版,user-controlled,用户决策使用 |
Mcp Server 提供通信方式
| 通信方式 | 说明 |
|---|---|
| stdio | 支持标准输入和输出流进行通信,主要用于本地集成、命令行工具等场景 |
| sse | 支持使用 HTTP POST 请求进行服务器到客户端流式处理,以实现客户端到服务器的通信 |
对于以上概念不清晰的,大家在回去阅读# MCP:MCP 是什么?带你深入MCP核心概念!
Java SDK 源码
- transport 通信协议
- HttpServletSseServerTransport sse方式
- StdioServerTransport 本地标准方式
- Mcp Server Mcp server 通用接口 内部 AsyncSpec 和 SyncSpec 类分别创建异步和同步 Mcp Server
- McpAsyncServer 创建异步 mcp server
- McpSyncServer 创建同步 mcp server
- McpServerFeatures Mcp Server 特性定义,比如服务器的版本、能力、工具、提示词等管理
对于源码大家自行阅读,代码量并不是很大,在后续的示例中,我们就不使用原生的SDK了,直接使用 Spring AI框架提供的 Starter 实现。
如果你真的想使用 SDK 自行实现,你也可以参考 Spring AI 框架去实现。下面我们将演示如何实现一个 Mcp Server,并提供 tools、resources 以及 prompts 能力,演示如何调用。
Spring AI 实现 Stdio Mcp Server
使用 Spring AI 框架实现一个简单的 Mcp Server,提供两个工具方法
引入依赖包
<properties>
<java.version>17</java.version>
<spring.ai.version>1.0.0-SNAPSHOT</spring.ai.version>
<mcp.version>0.7.0-SNAPSHOT</mcp.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring.ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-bom</artifactId>
<version>${mcp.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
配置文件配置项
spring.main.web-application-type=none
# 注意:一定要禁用 banner 和 console 日志
spring.main.banner-mode=off
logging.pattern.console=
spring.ai.mcp.server.enabled=true
spring.ai.mcp.server.name=my-tools-server
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.stdio=true
logging.file.name=spring-ai-mcp-server/target/target/mcp.mytools.log
Mcp Server 实现
package com.ivy.mcp.server;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbacks;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
// 将定义的工具转换为可以被Mcp使用的工具
@Bean
public List<ToolCallback> myTools(MyTools myTools) {
return List.of(ToolCallbacks.from(myTools));
}
@Service
public static class MyTools {
@Tool(description = "add two numbers")
public Integer add(@ToolParam(description = "first number") int a,
@ToolParam(description = "second number") int b) {
return a + b;
}
@Tool(description = "get current time")
public LocalDateTime getCurrentTime() {
return LocalDateTime.now();
}
}
}
工具声明与注册
除了如上示例代码给出的方式创建 Tools外,还有如下两种方式;
- 第一种方式:
@Bean
public ToolCallbackProvider tools(MyTools myTools) {
return MethodToolCallbackProvider.builder().toolObjects(myTools).build();
}
- 第二种方式
@Bean
public ToolCallbackProvider tools() {
return ToolCallbackProvider.from(
FunctionToolCallback.builder("toUpperCase", (Function<ToUpperCaseInput, String>) input -> input.input().toUpperCase())
.description("convert string to uppercase")
.inputType(ToUpperCaseInput.class)
.build()
);
}
大家思考一个问题:为什么工具这么声明就会被 Mcp Server 发现并且注册的??并且Mcp Server tools的规范是
McpServerFeatures.SyncToolRegistration。答案:玄机就在于
McpServerAutoConfiguration自动注册类中,经过 1-> 4步,将定义的工具转换为 MCP tools 规范并设置到 MCP server中,将Spring AI tools 无缝与 MCP server tools 规范集成。
调用工具示例
package com.ivy.mcp.stdio.client;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.transport.ServerParameters;
import io.modelcontextprotocol.client.transport.StdioClientTransport;
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
import io.modelcontextprotocol.spec.McpSchema.ListToolsResult;
import java.util.Map;
public class ClientToolsStdio {
public static void main(String[] args) {
var stdioParams = ServerParameters.builder("java")
.args("-jar",
"mcp-server/spring-ai-mcp-stdio-server/target/spring-ai-mcp-stdio-server-1.0.0-SNAPSHOT.jar")
.build();
var transport = new StdioClientTransport(stdioParams);
try (var client = McpClient.sync(transport).build()) {
client.initialize();
ListToolsResult toolsList = client.listTools();
System.out.println("Available Tools = " + toolsList);
CallToolResult sumResult = client.callTool(new CallToolRequest("add",
Map.of("a", 1, "b", 2)));
System.out.println("add a+ b = " + sumResult.content().get(0));
CallToolResult currentTimResult = client.callTool(new CallToolRequest("getCurrentTime", Map.of()));
System.out.println("current time Response = " + currentTimResult);
CallToolResult toUpperCaseResult = client.callTool(new CallToolRequest("toUpperCase", Map.of("input", "hello")));;
System.out.println("toUpperCaseResult = " + toUpperCaseResult);
}
}
}
资源声明与注册
@Bean
public List<McpServerFeatures.SyncResourceRegistration> syncResourceRegistrations() {
var systemInfoResource = new McpSchema.Resource(
"system://info",
"System Information",
"Provides basic system information including Java version, OS, etc.",
"application/json", null
);
var resourceRegistration = new McpServerFeatures.SyncResourceRegistration(systemInfoResource, (request) -> {
try {
var systemInfo = Map.of(
"javaVersion", System.getProperty("java.version"),
"osName", System.getProperty("os.name"),
"osVersion", System.getProperty("os.version"),
"osArch", System.getProperty("os.arch"),
"processors", Runtime.getRuntime().availableProcessors(),
"timestamp", System.currentTimeMillis());
String jsonContent = new ObjectMapper().writeValueAsString(systemInfo);
return new McpSchema.ReadResourceResult(
List.of(new McpSchema.TextResourceContents(request.uri(), "application/json", jsonContent)));
}
catch (Exception e) {
throw new RuntimeException("Failed to generate system info", e);
}
});
return List.of(resourceRegistration);
}
调用资源示例
package com.ivy.mcp.stdio.client;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.transport.ServerParameters;
import io.modelcontextprotocol.client.transport.StdioClientTransport;
import io.modelcontextprotocol.spec.McpSchema;
import java.util.List;
public class ClientResourceStdio {
public static void main(String[] args) {
var stdioParams = ServerParameters.builder("java")
.args("-jar",
"mcp-server/spring-ai-mcp-stdio-server/target/spring-ai-mcp-stdio-server-1.0.0-SNAPSHOT.jar")
.build();
var transport = new StdioClientTransport(stdioParams);
try (var client = McpClient.sync(transport).build()) {
client.initialize();
// McpSchema.ListResourcesResult resources = client.listResources();
// System.out.println("Available Resources = " + resources);
McpSchema.ReadResourceResult readResourceResult = client.readResource(
new McpSchema.ReadResourceRequest("system://info")
);
List<McpSchema.ResourceContents> contents = readResourceResult.contents();
contents.forEach(content -> {
if (content.mimeType().equals("application/json")) {
McpSchema.TextResourceContents text = (McpSchema.TextResourceContents) content;
System.out.println(text.text());
}
});
}
}
}
提示词模板声明与注册
@Bean
public List<McpServerFeatures.SyncPromptRegistration> promptRegistrations() {
var prompt = new McpSchema.Prompt(
"greeting",
"A friendly greeting prompt",
List.of(
new McpSchema.PromptArgument("name", "The name to greet", true)
)
);
var promptRegistration = new McpServerFeatures.SyncPromptRegistration(
prompt, getPromptRequest -> {
String nameArgument = (String) getPromptRequest.arguments().get("name");
if (nameArgument == null) {
nameArgument = "friend";
}
var userMessage = new McpSchema.PromptMessage(McpSchema.Role.USER,
new McpSchema.TextContent("Hello " + nameArgument + "! How can I assist you today?"));
return new McpSchema.GetPromptResult("A personalized greeting message", List.of(userMessage));
});
return List.of(promptRegistration);
}
对于提示词模板,这里只是简单的演示了如何声明一个 Prompt 模板,具体的使用根据自身的业务场景,定一个动态的提示词模板或者是一个提示词的工作流等。
调用提示词模板示例
package com.ivy.mcp.stdio.client;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.transport.ServerParameters;
import io.modelcontextprotocol.client.transport.StdioClientTransport;
import io.modelcontextprotocol.spec.McpSchema;
import java.util.List;
import java.util.Map;
public class ClientPromptStdio {
public static void main(String[] args) {
var stdioParams = ServerParameters.builder("java")
.args("-jar",
"mcp-server/spring-ai-mcp-stdio-server/target/spring-ai-mcp-stdio-server-1.0.0-SNAPSHOT.jar")
.build();
var transport = new StdioClientTransport(stdioParams);
try (var client = McpClient.sync(transport).build()) {
client.initialize();
McpSchema.ListPromptsResult listPromptsResult = client.listPrompts();
System.out.println("Available Prompts = " + listPromptsResult);
System.out.println("\n\n\n\n");
McpSchema.GetPromptResult prompt = client.getPrompt(
new McpSchema.GetPromptRequest("greeting", Map.of("name", "ivy"))
);
prompt.messages().forEach(
message -> System.out.println(
message.role().name() + " -> " + ((McpSchema.TextContent)message.content()).text()
)
);
}
}
}
文末总结
本文使用 Spring AI Mcp 能力实现一个简单 Mcp Server。并使用 Stdio 方式调用。大家先对 Mcp server的开发有一个简单的入门理解,后续会逐步深入开发,并且与大模型结合实现更为强大的 Agent 示例。
本文源码示例: github.com/Fj-ivy/clau…
欢迎大家一块来学习~~~