一、mcp-server构建
可以直接使用spring-ai的demo,https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webflux-server
二、mcp-server测试
使用vscode的cline作为mcp-client进行测试,添加自定义mcp-server
{
"mcpServers": {
"my-weather": {
"autoApprove": [
"getWeatherForecastByLocation",
"getAlerts"
],
"disabled": false,
"timeout": 60,
"url": "http://localhost:8080/sse",
"transportType": "sse"
}
}
}
成功添加后,会出现类似下面的结果
本地测试后,提示超时
Error executing MCP tool:
MCP error -32001: Request timed out
后面使用wireshark抓包发现,请求的时候没有响应,猜测是本地代理导致的
关闭代理后重试,有时候可以成功获取结果
猜测是里面获取了国外的接口,很容易导致超时,因此自己新增加一个简单的方法,如下:
# 添加到项目中的WeatherService
@Tool(description = "Get score for name. Input is someone's name (e.g. lisi)")
public String getScore(String name) {
return switch (name) {
case "lisi" -> "80.0";
case "zhangsan" -> "90.5";
default -> "60";
};
}
添加上面的自定义getScore后,终于可以顺利获取到响应了
lisi的分数是多少?
------------------------------------------------------------------
1. 检查了当前工作目录和文件列表,没有发现任何包含分数数据的文件
2. 注意到有一个名为"my-weather"的MCP服务器,其中提供了"getScore"工具
3. 该工具的描述是"Get score for name. Input is someone's name (e.g. lisi)"
4. 这似乎正是我们需要的功能,可以直接查询名字对应的分数
5. 需要调用MCP服务器的getScore工具,参数为name: "lisi"
------------------------------------------------------------------
Get score for name. Input is someone's name (e.g. lisi)
Arguments
{
"name": "lisi"
}
Resonpse
"80.0"
------------------------------------------------------------------
lisi的分数是80.0
三、mcp协议分析
1、添加mcp-server后客户端获取信息
会根据配置的url去发起sse请求,Accept: text/event-stream
然后mcp-server就会返回一堆的信息回来,包括tools列表以及tool描述信息
GET /sse HTTP/1.1
host: localhost:8080
connection: keep-alive
Accept: text/event-stream
accept-language: *
sec-fetch-mode: cors
user-agent: node
pragma: no-cache
cache-control: no-cache
accept-encoding: gzip, deflate
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/event-stream;charset=UTF-8
51
event:endpoint
data:/mcp/message?sessionId=100a5605-717f-4a06-ac0c-13a73e36091d
ce
event:message
data:{"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2024-11-05","capabilities":{"logging":{},"tools":{"listChanged":true}},"serverInfo":{"name":"my-weather-server","version":"0.0.1"}}}
330
event:message
data:{"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"toUpperCase","description":"Put the text to upper case","inputSchema":{"type":"object","properties":{"input":{"type":"string"}},"required":["input"],"additionalProperties":false}},{"name":"getAlerts","description":"Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY)","inputSchema":{"type":"object","properties":{"state":{"type":"string"}},"required":["state"],"additionalProperties":false}},{"name":"getWeatherForecastByLocation","description":"Get weather forecast for a specific latitude/longitude","inputSchema":{"type":"object","properties":{"latitude":{"type":"number","format":"double"},"longitude":{"type":"number","format":"double"}},"required":["latitude","longitude"],"additionalProperties":false}}]}}
72
event:message
data:{"jsonrpc":"2.0","id":2,"error":{"code":-32601,"message":"Method not found: resources/list"}}
7c
event:message
data:{"jsonrpc":"2.0","id":3,"error":{"code":-32601,"message":"Method not found: resources/templates/list"}}
2、/mcp/message请求
这个请求貌似和第一个/sse的关联性比较强
POST /mcp/message?sessionId=100a5605-717f-4a06-ac0c-13a73e36091d HTTP/1.1
host: localhost:8080
connection: keep-alive
content-type: application/json
accept: */*
accept-language: *
sec-fetch-mode: cors
user-agent: node
accept-encoding: gzip, deflate
content-length: 155
{"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"Cline","version":"3.15.5"}},"jsonrpc":"2.0","id":0}HTTP/1.1 200 OK
content-length: 0
POST /mcp/message?sessionId=100a5605-717f-4a06-ac0c-13a73e36091d HTTP/1.1
host: localhost:8080
connection: keep-alive
content-type: application/json
accept: */*
accept-language: *
sec-fetch-mode: cors
user-agent: node
accept-encoding: gzip, deflate
content-length: 54
{"method":"notifications/initialized","jsonrpc":"2.0"}HTTP/1.1 200 OK
content-length: 0
POST /mcp/message?sessionId=100a5605-717f-4a06-ac0c-13a73e36091d HTTP/1.1
host: localhost:8080
connection: keep-alive
content-type: application/json
accept: */*
accept-language: *
sec-fetch-mode: cors
user-agent: node
accept-encoding: gzip, deflate
content-length: 50
{"method":"resources/list","jsonrpc":"2.0","id":2}HTTP/1.1 200 OK
content-length: 0
3、超时取消响应
会返回notifications/cancelled
POST /mcp/message?sessionId=d851758f-11f1-4c2a-9060-ee83d9ae88b1 HTTP/1.1
host: localhost:8080
connection: keep-alive
content-type: application/json
accept: */*
accept-language: *
sec-fetch-mode: cors
user-agent: node
accept-encoding: gzip, deflate
content-length: 134
{"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":4,"reason":"McpError: MCP error -32001: Request timed out"}}HTTP/1.1 200 OK
content-length: 0
4、一个成功的业务请求响应:比如获取lisi的分数,mcp-server中实现了一个getScore的方法
发起了/mcp/message请求,里面包含了getScore方法和参数lisi
返回的响应是在/sse请求后续的event中返回的,76的event中有结果80.0
# 先贴一下cline中的过程
1. 检查了当前工作目录和文件列表,没有发现任何包含分数数据的文件
2. 注意到有一个名为"my-weather"的MCP服务器,其中提供了"getScore"工具
3. 该工具的描述是"Get score for name. Input is someone's name (e.g. lisi)"
4. 这似乎正是我们需要的功能,可以直接查询名字对应的分数
5. 需要调用MCP服务器的getScore工具,参数为name: "lisi"
------------------------------------------------------------------
POST /mcp/message?sessionId=52e8afc3-8ca1-489a-bba5-ad65c19ab57b HTTP/1.1
host: localhost:8080
connection: keep-alive
content-type: application/json
accept: */*
accept-language: *
sec-fetch-mode: cors
user-agent: node
accept-encoding: gzip, deflate
content-length: 103
{"method":"tools/call","params":{"name":"getScore","arguments":{"name":"lisi"}},"jsonrpc":"2.0","id":4}HTTP/1.1 200 OK
content-length: 0
------------------------------------------------------------------
GET /sse HTTP/1.1
host: localhost:8080
connection: keep-alive
Accept: text/event-stream
accept-language: *
sec-fetch-mode: cors
user-agent: node
pragma: no-cache
cache-control: no-cache
accept-encoding: gzip, deflate
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/event-stream;charset=UTF-8
51
event:endpoint
data:/mcp/message?sessionId=52e8afc3-8ca1-489a-bba5-ad65c19ab57b
ce
event:message
data:{"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2024-11-05","capabilities":{"logging":{},"tools":{"listChanged":true}},"serverInfo":{"name":"my-weather-server","version":"0.0.1"}}}
405
event:message
data:{"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"toUpperCase","description":"Put the text to upper case","inputSchema":{"type":"object","properties":{"input":{"type":"string"}},"required":["input"],"additionalProperties":false}},{"name":"getAlerts","description":"Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY)","inputSchema":{"type":"object","properties":{"state":{"type":"string"}},"required":["state"],"additionalProperties":false}},{"name":"getWeatherForecastByLocation","description":"Get weather forecast for a specific latitude/longitude","inputSchema":{"type":"object","properties":{"latitude":{"type":"number","format":"double"},"longitude":{"type":"number","format":"double"}},"required":["latitude","longitude"],"additionalProperties":false}},{"name":"getScore","description":"Get score for name. Input is someone's name (e.g. lisi)","inputSchema":{"type":"object","properties":{"name":{"type":"string"}},"required":["name"],"additionalProperties":false}}]}}
72
event:message
data:{"jsonrpc":"2.0","id":2,"error":{"code":-32601,"message":"Method not found: resources/list"}}
7c
event:message
data:{"jsonrpc":"2.0","id":3,"error":{"code":-32601,"message":"Method not found: resources/templates/list"}}
76
event:message
data:{"jsonrpc":"2.0","id":4,"result":{"content":[{"type":"text","text":"\"80.0\""}],"isError":false}}
mcp
登陆发表评论