大模型MCP原理及实践
文章目录
- 理解MCP
- 摸一摸MCP server和MCP client协同机制
- python不使用mcp sdk实现MCP server
- python使用mcp sdk实现MCP server
- JAVA使用mcp sdk(spring ai版)实现MCP server
- 再看本地Tool调用
理解MCP
看了一些MCP(Model Context Protocol)可能还是不太清楚这是什么东西,其余与其说MCP,还不如说MTP(Model Tools Protocl)。从官方或langchain等其他涉及MCP相关开源项目看,当前MCP发展阶段主要是工具调用方向。MCP官方提供了一些mcp server实现案例,这几个包括stdio/sse两种通讯方式,参考每个案例的README就可以爬起来,这里提供了多种语言的实现案例。自己实现一个mcp server也是比较容易的,mcp提供了部分语言sdk,实现非常容易,即使不使用sdk自己实现也很简单。client一般集成在其他应用程序中,使用langchain sdk可简单实现调用,参考langchain4j-examples
摸一摸MCP server和MCP client协同机制
实现参考 mcp-example/src/main/java/dev/langchain4j/example/mcp/McpToolsExampleOverStdio.java, client端实现主要使用了langchain4j-mcp这个maven依赖。该案例使用mcp server使用npm install @modelcontextprotocol/server-filesystem@0.6.2装上即可,client启动后或拉起这个mcp server进程并通过stdio方式与之通讯
package com.example.demo;import dev.langchain4j.mcp.McpToolProvider;
import dev.langchain4j.mcp.client.DefaultMcpClient;
import dev.langchain4j.mcp.client.McpClient;
import dev.langchain4j.mcp.client.transport.McpTransport;
import dev.langchain4j.mcp.client.transport.stdio.StdioMcpTransport;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.tool.ToolProvider;import java.io.File;
import java.util.List;public class McpToolsExampleOverStdio {public interface Bot {String chat(String prompt);}public static void main(String[] args) throws Exception {ChatModel model = OpenAiChatModel.builder().baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1") .apiKey("sk-f6a3b352639f4f1XXXXXXXXX") // 阿里百炼申请一个apikey,如果使用本地部署的大模型就不用设置apikey咯.modelName("qwen3-max").logRequests(true) // 打开日志摸机制.logResponses(true).build();
// 安装mcp server案例:npm install @modelcontextprotocol/server-filesystem@0.6.2McpTransport transport = new StdioMcpTransport.Builder().command(List.of("E:\\nodejs\\npm.cmd", "exec","@modelcontextprotocol/server-filesystem@0.6.2",// 允许server-filesystem访问的目录new File("D:\\test").getAbsolutePath())).logEvents(true).build();McpClient mcpClient = new DefaultMcpClient.Builder().transport(transport).build();ToolProvider toolProvider = McpToolProvider.builder().mcpClients(List.of(mcpClient)).build();Bot bot = AiServices.builder(Bot.class).chatModel(model).toolProvider(toolProvider).build();try {String response = bot.chat("文件夹D:\\test里有哪些文件");System.out.println("RESPONSE: " + response);} finally {mcpClient.close();}}
}
通过分析日志输出,总结出以下协调机制

python不使用mcp sdk实现MCP server
不使用sdk便于摸清底层实现
#!/usr/bin/env python3import sys
import json
import signal
from typing import Any, Dict# --- 工具定义 ---
TOOLS = {"get_weather": {"description": "Get the current weather in a given location","parameters": {"type": "object","properties": {"location": {"type": "string"}},"required": ["location"]}},"add": {"description": "Add two numbers","parameters": {"type": "object","properties": {"a": {"type": "number"},"b": {"type": "number"}},"required": ["a", "b"]}}
}def execute_tool(method: str, params: Dict[str, Any]) -> Any:if method == "get_weather":loc = params.get("location", "Unknown")return f"The weather in {loc} is sunny and 25"elif method == "add":return params.get("a", 0) + params.get("b", 0)else:raise ValueError(f"Tool '{method}' not found")
def get_tools_list():"""返回符合 MCP spec 的工具列表"""return [{"name": name,"description": tool["description"],"inputSchema": tool["parameters"] # 注意:MCP spec 用 inputSchema,不是 parameters}for name, tool in TOOLS.items()]def send_response(response: dict):"""stdout响应"""json_str = json.dumps(response, separators=(',', ':'))sys.stdout.write(json_str + '\n')sys.stdout.flush()def handle_request(data: dict):method = data.get("method")req_id = data.get("id")params = data.get("params", {})if method == "initialize":# 返回支持的工具列表tool_list = [{"name": name,"description": tool["description"],"parameters": tool["parameters"]}for name, tool in TOOLS.items()]result = {"tools": tool_list}send_response({"jsonrpc": "2.0","id": req_id,"result": result})elif method == "tools/list":tools = get_tools_list()send_response({"jsonrpc": "2.0","id": req_id,"result": {"tools": tools}})elif method == "tools/call":tool_name = params.get("name")tool_args = params.get("arguments", {})try:result = execute_tool(tool_name, tool_args)send_response({"jsonrpc": "2.0","id": req_id,"result":{"content":[{"type":"text","text":result}]}})except Exception as e:send_response({"jsonrpc": "2.0","id": req_id,"error": {"code": -32000, "message": str(e)}})else:send_response({"jsonrpc": "2.0","id": req_id,"error": {"code": -32601, "message": f"Method not found: {method}"}})def main():# 忽略 SIGPIPE(防止客户端关闭时崩溃)signal.signal(signal.SIGFPE, signal.SIG_DFL)try:for line in sys.stdin:line = line.strip()if not line:continuetry:request = json.loads(line)handle_request(request)except json.JSONDecodeError:# 忽略无效 JSONcontinueexcept KeyboardInterrupt:passexcept Exception as e:print(f"Error: {e}", file=sys.stderr)# 可选带环境打包python:python3 -m PyInstaller --onefile --console my_stdio_mcp_server.py
# 控制台测试tools/list接口:{"jsonrpc":"2.0","id":"1","method":"tools/list","params":{}}
# 控制台测试工具调用:{"jsonrpc":"2.0","id":"2","method":"tools/call","params":{"name":"get_weather","arguments":{"location":"Tokyo"}}}
if __name__ == "__main__":main()
mcp server调用
package com.example.demo;import dev.langchain4j.mcp.McpToolProvider;
import dev.langchain4j.mcp.client.DefaultMcpClient;
import dev.langchain4j.mcp.client.McpClient;
import dev.langchain4j.mcp.client.transport.McpTransport;
import dev.langchain4j.mcp.client.transport.stdio.StdioMcpTransport;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.observability.api.event.AiServiceCompletedEvent;
import dev.langchain4j.observability.api.listener.AiServiceCompletedListener;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.tool.ToolProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.File;
import java.util.List;public class McpToolsExampleOverStdio {public interface Bot {String chat(String prompt);}public static void main(String[] args) throws Exception {ChatModel model = OpenAiChatModel.builder().baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1").apiKey("sk-f6a3b352639f4f179fxxx").modelName("qwen3-max").logRequests(true).logResponses(true).build();McpTransport transport = new StdioMcpTransport.Builder().command(List.of("C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python310\\python.exe", "D:\\demo8\\my_stdio_mcp_server.py")).logEvents(true).build();McpClient mcpClient = new DefaultMcpClient.Builder().transport(transport).build();ToolProvider toolProvider = McpToolProvider.builder().mcpClients(List.of(mcpClient)).build();Bot bot = AiServices.builder(Bot.class).chatModel(model).toolProvider(toolProvider).build();try {String response = bot.chat("成都天气如何");System.out.println("RESPONSE: " + response);} finally {mcpClient.close();}}
}
python使用mcp sdk实现MCP server
安装依赖pip3 install mcp -i https://mirrors.aliyun.com/pypi/simple/ ,代码参考
import asyncio
from datetime import datetime, timedelta
from enum import Enum
import json
from typing import Sequencefrom zoneinfo import ZoneInfo
from tzlocal import get_localzone_name # ← returns "Europe/Paris", etc.from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent, ImageContent, EmbeddedResource
from mcp.shared.exceptions import McpErrorfrom pydantic import BaseModelclass TimeTools(str, Enum):GET_CURRENT_TIME = "get_current_time"CONVERT_TIME = "convert_time"class TimeResult(BaseModel):timezone: strdatetime: strday_of_week: stris_dst: boolclass TimeConversionResult(BaseModel):source: TimeResulttarget: TimeResulttime_difference: strclass TimeConversionInput(BaseModel):source_tz: strtime: strtarget_tz_list: list[str]def get_local_tz(local_tz_override: str | None = None) -> ZoneInfo:if local_tz_override:return ZoneInfo(local_tz_override)# Get local timezone from datetime.now()local_tzname = get_localzone_name()if local_tzname is not None:return ZoneInfo(local_tzname)# Default to UTC if local timezone cannot be determinedreturn ZoneInfo("UTC")def get_zoneinfo(timezone_name: str) -> ZoneInfo:try:return ZoneInfo(timezone_name)except Exception as e:raise McpError(f"Invalid timezone: {str(e)}")class TimeServer:def get_current_time(self, timezone_name: str) -> TimeResult:"""Get current time in specified timezone"""timezone = get_zoneinfo(timezone_name)current_time = datetime.now(timezone)return TimeResult(timezone=timezone_name,datetime=current_time.isoformat(timespec="seconds"),day_of_week=current_time.strftime("%A"),is_dst=bool(current_time.dst()),)def convert_time(self, source_tz: str, time_str: str, target_tz: str) -> TimeConversionResult:"""Convert time between timezones"""source_timezone = get_zoneinfo(source_tz)target_timezone = get_zoneinfo(target_tz)try:parsed_time = datetime.strptime(time_str, "%H:%M").time()except ValueError:raise ValueError("Invalid time format. Expected HH:MM [24-hour format]")now = datetime.now(source_timezone)source_time = datetime(now.year,now.month,now.day,parsed_time.hour,parsed_time.minute,tzinfo=source_timezone,)target_time = source_time.astimezone(target_timezone)source_offset = source_time.utcoffset() or timedelta()target_offset = target_time.utcoffset() or timedelta()hours_difference = (target_offset - source_offset).total_seconds() / 3600if hours_difference.is_integer():time_diff_str = f"{hours_difference:+.1f}h"else:# For fractional hours like Nepal's UTC+5:45time_diff_str = f"{hours_difference:+.2f}".rstrip("0").rstrip(".") + "h"return TimeConversionResult(source=TimeResult(timezone=source_tz,datetime=source_time.isoformat(timespec="seconds"),day_of_week=source_time.strftime("%A"),is_dst=bool(source_time.dst()),),target=TimeResult(timezone=target_tz,datetime=target_time.isoformat(timespec="seconds"),day_of_week=target_time.strftime("%A"),is_dst=bool(target_time.dst()),),time_difference=time_diff_str,)async def serve(local_timezone: str | None = None) -> None:server = Server("mcp-time")time_server = TimeServer()local_tz = str(get_local_tz(local_timezone))@server.list_tools()async def list_tools() -> list[Tool]:"""List available time tools."""return [Tool(name=TimeTools.GET_CURRENT_TIME.value,description="Get current time in a specific timezones",inputSchema={"type": "object","properties": {"timezone": {"type": "string","description": f"IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use '{local_tz}' as local timezone if no timezone provided by the user.",}},"required": ["timezone"],},),Tool(name=TimeTools.CONVERT_TIME.value,description="Convert time between timezones",inputSchema={"type": "object","properties": {"source_timezone": {"type": "string","description": f"Source IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use '{local_tz}' as local timezone if no source timezone provided by the user.",},"time": {"type": "string","description": "Time to convert in 24-hour format (HH:MM)",},"target_timezone": {"type": "string","description": f"Target IANA timezone name (e.g., 'Asia/Tokyo', 'America/San_Francisco'). Use '{local_tz}' as local timezone if no target timezone provided by the user.",},},"required": ["source_timezone", "time", "target_timezone"],},),]@server.call_tool()async def call_tool(name: str, arguments: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:"""Handle tool calls for time queries."""try:match name:case TimeTools.GET_CURRENT_TIME.value:timezone = arguments.get("timezone")if not timezone:raise ValueError("Missing required argument: timezone")result = time_server.get_current_time(timezone)case TimeTools.CONVERT_TIME.value:if not all(k in argumentsfor k in ["source_timezone", "time", "target_timezone"]):raise ValueError("Missing required arguments")result = time_server.convert_time(arguments["source_timezone"],arguments["time"],arguments["target_timezone"],)case _:raise ValueError(f"Unknown tool: {name}")return [TextContent(type="text", text=json.dumps(result.model_dump(), indent=2))]except Exception as e:raise ValueError(f"Error processing mcp-server-time query: {str(e)}")options = server.create_initialization_options()async with stdio_server() as (read_stream, write_stream):await server.run(read_stream, write_stream, options)if __name__ == '__main__':asyncio.run(serve(None))
mcp server调用
package com.example.demo;import dev.langchain4j.mcp.McpToolProvider;
import dev.langchain4j.mcp.client.DefaultMcpClient;
import dev.langchain4j.mcp.client.McpClient;
import dev.langchain4j.mcp.client.transport.McpTransport;
import dev.langchain4j.mcp.client.transport.stdio.StdioMcpTransport;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.observability.api.event.AiServiceCompletedEvent;
import dev.langchain4j.observability.api.listener.AiServiceCompletedListener;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.tool.ToolProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.File;
import java.util.List;public class McpToolsExampleOverStdio {public interface Bot {String chat(String prompt);}public static void main(String[] args) throws Exception {ChatModel model = OpenAiChatModel.builder().baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1").apiKey("sk-f6a3b352639f4f179f4028ddcXXXXXX").modelName("qwen3-max").logRequests(true).logResponses(true).build();McpTransport transport = new StdioMcpTransport.Builder()//.command(List.of("C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python310\\python.exe", "D:\\demo8\\my_stdio_mcp_server.py")).command(List.of("C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python310\\python.exe", "D:/1/servers-main/servers-main/src/time/src/mcp_server_time/server.py")).logEvents(true).build();McpClient mcpClient = new DefaultMcpClient.Builder().transport(transport).build();ToolProvider toolProvider = McpToolProvider.builder().mcpClients(List.of(mcpClient)).build();Bot bot = AiServices.builder(Bot.class).chatModel(model).toolProvider(toolProvider).build();try {String response = bot.chat("当前时区时间");System.out.println("RESPONSE: " + response);} finally {mcpClient.close();}}
}
JAVA使用mcp sdk(spring ai版)实现MCP server
mcp sdk Java 还是spring ai版本更easy
- 引入依赖
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server-webmvc</artifactId><version>1.0.3</version></dependency>
配置
server:port: 8080
spring:ai:mcp:server:name: mcp-server sse-message-endpoint: /mcp/message version: 1.0.0 type: SYNC instructions: "该tool提供用户信息" sse-endpoint: /sse capabilities:tool: true #启用/禁用工具功能resource: trueprompt: truecompletion: true
业务对接tool
package com.example.demo;import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class ToolConfig {@Beanpublic ToolCallbackProvider userTools(UserService userService) {return MethodToolCallbackProvider.builder().toolObjects(userService).build();}
}
package com.example.demo;import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
import java.util.Map;@Service
public class UserService {@Tool(name = "queryUserInfo", description = "根据id查询用户信息")public Map queryUserInfo(@ToolParam( description = "用户id") String id) {// 模拟DB查询return Map.of("id",id,"username","jwolf","age","18");}
}
mcp server调用
package com.example.demo;import dev.langchain4j.mcp.McpToolProvider;
import dev.langchain4j.mcp.client.DefaultMcpClient;
import dev.langchain4j.mcp.client.McpClient;
import dev.langchain4j.mcp.client.transport.McpTransport;
import dev.langchain4j.mcp.client.transport.http.HttpMcpTransport;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.tool.ToolProvider;import java.time.Duration;
import java.util.List;public class McpToolsExampleOverHttp {/*** This example uses the `server-everything` MCP server that showcases some aspects of the MCP protocol.* In particular, we use its 'add' tool that adds two numbers.* <p>* Before running this example, you need to start the `everything` server in SSE mode on localhost:3001.* Check out https://github.com/modelcontextprotocol/servers/tree/main/src/everything* and run `npm install` and `node dist/sse.js`.* <p>* Of course, feel free to swap out the server with any other MCP server.* <p>* Run the example and check the logs to verify that the model used the tool.*/public static void main(String[] args) throws Exception {ChatModel model = OpenAiChatModel.builder().baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1").apiKey("sk-f6a3b352639f4f179f4028ddcXXXXX").modelName("qwen3-max").logRequests(true).logResponses(true).build();McpTransport transport = new HttpMcpTransport.Builder().sseUrl("http://localhost:8080/sse").timeout(Duration.ofSeconds(60)).logRequests(true).logResponses(true).build();McpClient mcpClient = new DefaultMcpClient.Builder().transport(transport).build();ToolProvider toolProvider = McpToolProvider.builder().mcpClients(List.of(mcpClient)).build();Bot bot = AiServices.builder(Bot.class).chatModel(model).toolProvider(toolProvider).build();try {String response = bot.chat("用户12345信息");System.out.println(response);} finally {mcpClient.close();}}
}
再看本地Tool调用
其实MCP核心就是把Tool抽离出来了,实现解耦,复用,语言无关等,代码参考
package com.example.demo;import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI;public class ServiceWithToolsExample {// Please also check CustomerSupportApplication and CustomerSupportApplicationTest// from spring-boot-example modulestatic class Calculator {@Tool("Calculates the length of a string")int stringLength(String s) {System.out.println("Called stringLength with s='" + s + "'");return s.length();}@Tool("Calculates the sum of two numbers")int add(int a, int b) {System.out.println("Called add with a=" + a + ", b=" + b);return a + b;}@Tool("Calculates the square root of a number")double sqrt(int x) {System.out.println("Called sqrt with x=" + x);return Math.sqrt(x);}}interface Assistant {String chat(String userMessage);}public static void main(String[] args) {ChatModel model = OpenAiChatModel.builder().baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1").apiKey("sk-f6a3b352639f4f179f4028ddc2XXX").modelName("qwen3-max").logRequests(true).logResponses(true).build();Assistant assistant = AiServices.builder(Assistant.class).chatModel(model).tools(new Calculator()).chatMemory(MessageWindowChatMemory.withMaxMessages(10)).build();String question = "What is the square root of the sum of the numbers of letters in the words \"hello\" and \"world\"?";String answer = assistant.chat(question);System.out.println(answer);// The square root of the sum of the number of letters in the words "hello" and "world" is approximately 3.162.}
}
