forked from DevOps/deploy.stack
feat: 添加MCP时间服务器及相关工具
实现一个基于Golang的MCP时间服务器,提供获取当前时间和日期功能 包含客户端示例、安装脚本和详细文档 refactor: 优化磁盘巡检脚本以支持SAS和SSD硬盘 增强磁盘巡检脚本的兼容性,改进SMART信息解析逻辑 添加硬盘类型检测和更全面的错误处理 docs: 更新README和安装说明 添加MCP时间服务器的使用文档和API说明 完善磁盘巡检报告格式和内容
This commit is contained in:
152
mcpTimeServer/README.md
Normal file
152
mcpTimeServer/README.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# MCP Time Server
|
||||
|
||||
一个使用Golang实现的MCP(Model Context Protocol)服务器,提供获取当前系统时间和SSE(Server-Sent Events)实时时间流功能。
|
||||
|
||||
## 项目概述
|
||||
|
||||
这个服务器实现了自定义的MCP协议,允许客户端通过HTTP接口获取当前系统时间,并且支持通过SSE技术订阅实时时间更新流。服务器使用slog库进行日志记录,日志仅输出到控制台。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 提供获取当前系统时间的MCP工具(`get_current_time`)
|
||||
- 支持自定义时间格式
|
||||
- 实现SSE(Server-Sent Events)功能,提供实时时间流订阅(`subscribe_time_stream`)
|
||||
- 使用slog进行结构化日志记录
|
||||
- 基于HTTP实现MCP协议
|
||||
- 提供健康检查接口
|
||||
- 支持优雅关闭
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
mcpTimeServer/
|
||||
├── main.go # 服务器主程序
|
||||
├── go.mod # Go模块定义
|
||||
├── install.sh # 安装脚本
|
||||
└── README.md # 项目说明文档
|
||||
```
|
||||
|
||||
## 安装与配置
|
||||
|
||||
### 前提条件
|
||||
|
||||
- Go 1.21或更高版本
|
||||
|
||||
### 安装步骤
|
||||
|
||||
1. 克隆项目或进入项目目录
|
||||
|
||||
2. 运行安装脚本:
|
||||
```bash
|
||||
chmod +x install.sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
安装脚本会检查Go环境、安装依赖并编译项目。
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 启动服务器
|
||||
|
||||
```bash
|
||||
./mcp_time_server
|
||||
```
|
||||
|
||||
服务器启动后,会在8080端口监听HTTP请求。
|
||||
|
||||
## API说明
|
||||
|
||||
### MCP协议接口
|
||||
|
||||
#### 提交MCP请求
|
||||
|
||||
- **URL**: `/mcp/v1/submit`
|
||||
- **方法**: `POST`
|
||||
- **Content-Type**: `application/json`
|
||||
|
||||
**请求体示例**:
|
||||
```json
|
||||
{
|
||||
"data": {"format": "2006-01-02 15:04:05"}, // 可选参数
|
||||
"type": "get_current_time",
|
||||
"timestamp": 1699999999
|
||||
}
|
||||
```
|
||||
|
||||
#### 可用工具
|
||||
|
||||
1. **get_current_time**
|
||||
- **参数**: `format`(可选)- 时间格式字符串,使用Go的时间格式语法(如"2006-01-02 15:04:05")
|
||||
- **返回结果**:
|
||||
```json
|
||||
{
|
||||
"current_time": "格式化的时间字符串",
|
||||
"timestamp": 时间戳(Unix时间,秒)
|
||||
}
|
||||
```
|
||||
|
||||
2. **subscribe_time_stream**
|
||||
- **参数**:
|
||||
- `interval`(可选)- 时间更新间隔(秒),默认为1秒
|
||||
- `format`(可选)- 时间格式字符串
|
||||
- **返回结果**:
|
||||
```json
|
||||
{
|
||||
"stream_id": "唯一的流标识符",
|
||||
"sse_url": "http://localhost:8080/sse",
|
||||
"interval": 更新间隔(秒)
|
||||
}
|
||||
```
|
||||
|
||||
### SSE接口
|
||||
|
||||
- **URL**: `/sse`
|
||||
- **方法**: `GET`
|
||||
- **Content-Type**: `text/event-stream`
|
||||
|
||||
连接后,服务器会以指定的间隔发送时间更新事件。
|
||||
|
||||
### 健康检查接口
|
||||
|
||||
- **URL**: `/health`
|
||||
- **方法**: `GET`
|
||||
- **返回**: 状态码200表示服务器正常运行
|
||||
|
||||
## 测试方法
|
||||
|
||||
服务器启动后,可以使用以下方式测试:
|
||||
|
||||
1. **健康检查**:
|
||||
```bash
|
||||
curl http://localhost:8080/health
|
||||
```
|
||||
|
||||
2. **获取当前时间**:
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/mcp/v1/submit -H 'Content-Type: application/json' -d '{"data":{},"type":"get_current_time","timestamp":'$(date +%s)'}'
|
||||
```
|
||||
|
||||
3. **使用自定义格式获取当前时间**:
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/mcp/v1/submit -H 'Content-Type: application/json' -d '{"data":{"format":"2006-01-02 15:04:05"},"type":"get_current_time","timestamp":'$(date +%s)'}'
|
||||
```
|
||||
|
||||
4. **订阅时间流**:
|
||||
```bash
|
||||
curl http://localhost:8080/sse
|
||||
```
|
||||
或直接在浏览器中访问 `http://localhost:8080/sse`
|
||||
|
||||
## 依赖说明
|
||||
|
||||
- github.com/google/uuid v1.6.0:用于生成唯一标识符
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 服务器默认监听在8080端口
|
||||
- 日志仅输出到控制台,不会写入文件
|
||||
- SSE连接会在客户端断开或服务器关闭时终止
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
5
mcpTimeServer/go.mod
Normal file
5
mcpTimeServer/go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module mcpTimeServer
|
||||
|
||||
go 1.21
|
||||
|
||||
require github.com/google/uuid v1.6.0
|
||||
71
mcpTimeServer/install.sh
Normal file
71
mcpTimeServer/install.sh
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/bin/bash
|
||||
|
||||
# MCP Time Server 安装脚本
|
||||
|
||||
# 设置颜色
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # 无颜色
|
||||
|
||||
# 检查是否安装了Go
|
||||
echo -e "${BLUE}检查Go环境...${NC}"
|
||||
if ! command -v go &> /dev/null
|
||||
then
|
||||
echo -e "${RED}错误: 未安装Go。请先安装Go 1.21或更高版本。${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查Go版本
|
||||
GO_VERSION=$(go version | awk '{print $3}' | sed 's/go//')
|
||||
GO_MAJOR=$(echo $GO_VERSION | cut -d. -f1)
|
||||
GO_MINOR=$(echo $GO_VERSION | cut -d. -f2)
|
||||
|
||||
if [ $GO_MAJOR -lt 1 ] || ([ $GO_MAJOR -eq 1 ] && [ $GO_MINOR -lt 21 ]); then
|
||||
echo -e "${RED}错误: Go版本过低 ($GO_VERSION)。请安装Go 1.21或更高版本。${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}已安装Go ($GO_VERSION)${NC}"
|
||||
|
||||
# 检查当前目录
|
||||
echo -e "${BLUE}\n检查项目目录...${NC}"
|
||||
if [ ! -f "main.go" ]; then
|
||||
echo -e "${RED}错误: 请在包含main.go的项目根目录下运行此脚本。${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}项目目录正确${NC}"
|
||||
|
||||
# 安装依赖
|
||||
echo -e "${BLUE}\n安装项目依赖...${NC}"
|
||||
go mod tidy
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}安装依赖失败,请检查网络连接。${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}依赖安装成功${NC}"
|
||||
|
||||
# 编译项目
|
||||
echo -e "${BLUE}\n编译项目...${NC}"
|
||||
go build -o mcp_time_server
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}编译失败。${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod +x mcp_time_server
|
||||
echo -e "${GREEN}编译成功,生成可执行文件: mcp_time_server${NC}"
|
||||
|
||||
# 显示使用说明
|
||||
echo -e "\n${GREEN}安装完成!${NC}"
|
||||
echo -e "${BLUE}\n使用说明:${NC}"
|
||||
echo -e "1. 启动服务器: ./mcp_time_server"
|
||||
echo -e "2. 服务器启动后,可以通过以下方式测试:"
|
||||
echo -e " - 健康检查: curl http://localhost:8080/health"
|
||||
echo -e " - 获取当前时间: curl -X POST http://localhost:8080/mcp/v1/submit -H 'Content-Type: application/json' -d '{\"data\":{},\"type\":\"get_current_time\",\"timestamp\":$(date +%s)}'"
|
||||
echo -e " - 订阅时间流: 使用浏览器或SSE客户端访问 http://localhost:8080/sse"
|
||||
echo -e "\n${YELLOW}注意: 服务器默认监听在8080端口。${NC}"
|
||||
echo -e "\n${GREEN}MCP Time Server安装成功!${NC}"
|
||||
385
mcpTimeServer/main.go
Normal file
385
mcpTimeServer/main.go
Normal file
@@ -0,0 +1,385 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// MCPRequest MCP请求结构体
|
||||
type MCPRequest struct {
|
||||
Data interface{} `json:"data"`
|
||||
Type string `json:"type"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
// MCPResponse MCP响应结构体
|
||||
type MCPResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
// SSEClient SSE客户端连接管理
|
||||
type SSEClient struct {
|
||||
ID string
|
||||
Channel chan []byte
|
||||
}
|
||||
|
||||
// SSEManager SSE管理器
|
||||
type SSEManager struct {
|
||||
clients map[string]*SSEClient
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// NewSSEManager 创建新的SSE管理器
|
||||
func NewSSEManager() *SSEManager {
|
||||
return &SSEManager{
|
||||
clients: make(map[string]*SSEClient),
|
||||
}
|
||||
}
|
||||
|
||||
// AddClient 添加SSE客户端
|
||||
func (m *SSEManager) AddClient(clientID string) *SSEClient {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
client := &SSEClient{
|
||||
ID: clientID,
|
||||
Channel: make(chan []byte, 10),
|
||||
}
|
||||
m.clients[clientID] = client
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// RemoveClient 移除SSE客户端
|
||||
func (m *SSEManager) RemoveClient(clientID string) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if client, exists := m.clients[clientID]; exists {
|
||||
close(client.Channel)
|
||||
delete(m.clients, clientID)
|
||||
}
|
||||
}
|
||||
|
||||
// Broadcast 广播消息给所有SSE客户端
|
||||
func (m *SSEManager) Broadcast(message []byte) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
for _, client := range m.clients {
|
||||
select {
|
||||
case client.Channel <- message:
|
||||
default:
|
||||
// 如果客户端通道已满,跳过
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化时间,处理可能的格式错误
|
||||
func formatTime(t time.Time, format string) (string, error) {
|
||||
if format == "" {
|
||||
return t.Format(time.RFC3339), nil
|
||||
}
|
||||
|
||||
// 尝试使用自定义格式
|
||||
formatted := t.Format(format)
|
||||
if formatted == format {
|
||||
// 如果格式化后的结果与格式字符串相同,说明格式无效
|
||||
return "", errors.New("无效的时间格式")
|
||||
}
|
||||
|
||||
return formatted, nil
|
||||
}
|
||||
|
||||
// 处理获取当前时间请求
|
||||
func handleGetCurrentTime(data map[string]interface{}) (map[string]interface{}, error) {
|
||||
// 获取当前时间
|
||||
now := time.Now()
|
||||
|
||||
// 获取可选的格式参数
|
||||
format, _ := data["format"].(string)
|
||||
|
||||
// 格式化时间
|
||||
formattedTime, err := formatTime(now, format)
|
||||
if err != nil {
|
||||
// 如果格式无效,使用默认格式
|
||||
formattedTime = now.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
result := map[string]interface{}{
|
||||
"current_time": formattedTime,
|
||||
"timestamp": now.Unix(),
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 处理订阅时间流请求
|
||||
func handleSubscribeTimeStream(data map[string]interface{}) (map[string]interface{}, error) {
|
||||
// 生成唯一的流ID
|
||||
streamID := uuid.New().String()
|
||||
|
||||
// 获取可选的间隔参数(默认1秒)
|
||||
interval := 1.0
|
||||
if intervalVal, ok := data["interval"].(float64); ok {
|
||||
if intervalVal > 0 {
|
||||
interval = intervalVal
|
||||
}
|
||||
}
|
||||
|
||||
// 获取可选的格式参数
|
||||
format, _ := data["format"].(string)
|
||||
|
||||
// 存储订阅信息(实际项目中可能需要更复杂的存储机制)
|
||||
// 这里我们只是返回流信息,实际的SSE连接会在/sse端点建立
|
||||
|
||||
// 返回结果
|
||||
result := map[string]interface{}{
|
||||
"stream_id": streamID,
|
||||
"sse_url": "http://localhost:8080/sse",
|
||||
"interval": interval,
|
||||
"format": format,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 处理MCP请求提交
|
||||
func handleSubmit(w http.ResponseWriter, r *http.Request) {
|
||||
// 检查请求方法
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析请求体
|
||||
var request MCPRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
response := MCPResponse{
|
||||
Success: false,
|
||||
Message: "Invalid request body",
|
||||
Timestamp: time.Now().Unix(),
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(response)
|
||||
return
|
||||
}
|
||||
|
||||
// 生成请求ID
|
||||
requestID := uuid.New().String()
|
||||
|
||||
// 根据工具类型处理请求
|
||||
var result map[string]interface{}
|
||||
var err error
|
||||
switch request.Type {
|
||||
case "get_current_time":
|
||||
// 确保data是map类型
|
||||
dataMap, ok := request.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
dataMap = make(map[string]interface{})
|
||||
}
|
||||
result, err = handleGetCurrentTime(dataMap)
|
||||
case "subscribe_time_stream":
|
||||
// 确保data是map类型
|
||||
dataMap, ok := request.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
dataMap = make(map[string]interface{})
|
||||
}
|
||||
result, err = handleSubscribeTimeStream(dataMap)
|
||||
default:
|
||||
err = errors.New("未知的工具类型")
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
response := MCPResponse{
|
||||
Success: err == nil,
|
||||
Message: func() string { if err != nil { return err.Error() } return "" }(),
|
||||
Data: result,
|
||||
RequestID: requestID,
|
||||
Timestamp: time.Now().Unix(),
|
||||
}
|
||||
|
||||
// 返回响应
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(response)
|
||||
|
||||
// 记录日志
|
||||
slog.Debug("处理MCP请求", "request_id", requestID, "tool_type", request.Type, "success", err == nil)
|
||||
}
|
||||
|
||||
// 处理SSE连接
|
||||
func handleSSE(w http.ResponseWriter, r *http.Request, sseManager *SSEManager) {
|
||||
// 设置响应头
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Connection", "keep-alive")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
// 获取或生成客户端ID
|
||||
clientID := r.URL.Query().Get("client_id")
|
||||
if clientID == "" {
|
||||
clientID = uuid.New().String()
|
||||
}
|
||||
|
||||
// 添加客户端到管理器
|
||||
client := sseManager.AddClient(clientID)
|
||||
defer sseManager.RemoveClient(clientID)
|
||||
|
||||
// 发送初始连接确认事件
|
||||
fmt.Fprintf(w, "event: connected\ndata: {\"client_id\":\"%s\"}\n\n", clientID)
|
||||
flusher, ok := w.(http.Flusher)
|
||||
if !ok {
|
||||
slog.Error("不支持SSE", "client_id", clientID)
|
||||
return
|
||||
}
|
||||
flusher.Flush()
|
||||
|
||||
// 获取时间格式参数(如果有)
|
||||
format := r.URL.Query().Get("format")
|
||||
|
||||
// 获取时间间隔参数(如果有)
|
||||
interval := 1.0
|
||||
if intervalStr := r.URL.Query().Get("interval"); intervalStr != "" {
|
||||
fmt.Sscanf(intervalStr, "%f", &interval)
|
||||
if interval <= 0 {
|
||||
interval = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
// 创建上下文用于取消操作
|
||||
ctx, cancel := context.WithCancel(r.Context())
|
||||
defer cancel()
|
||||
|
||||
// 创建定时器
|
||||
ticker := time.NewTicker(time.Duration(interval * float64(time.Second)))
|
||||
defer ticker.Stop()
|
||||
|
||||
slog.Info("SSE连接已建立", "client_id", clientID, "interval", interval, "format", format)
|
||||
|
||||
// 发送时间更新
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// 客户端断开连接
|
||||
slog.Info("SSE连接已断开", "client_id", clientID)
|
||||
return
|
||||
case now := <-ticker.C:
|
||||
// 格式化时间
|
||||
var formattedTime string
|
||||
if format == "" {
|
||||
formattedTime = now.Format(time.RFC3339)
|
||||
} else {
|
||||
var err error
|
||||
formattedTime, err = formatTime(now, format)
|
||||
if err != nil {
|
||||
// 如果格式无效,使用默认格式
|
||||
formattedTime = now.Format(time.RFC3339)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建时间更新事件
|
||||
timeData := map[string]interface{}{
|
||||
"current_time": formattedTime,
|
||||
"timestamp": now.Unix(),
|
||||
"interval": interval,
|
||||
}
|
||||
|
||||
// 转换为JSON
|
||||
dataJSON, _ := json.Marshal(timeData)
|
||||
|
||||
// 发送SSE事件
|
||||
fmt.Fprintf(w, "event: time_update\ndata: %s\n\n", string(dataJSON))
|
||||
flusher.Flush()
|
||||
slog.Debug("发送SSE时间更新", "client_id", clientID, "time", formattedTime)
|
||||
case message := <-client.Channel:
|
||||
// 发送广播消息
|
||||
fmt.Fprintf(w, "event: broadcast\ndata: %s\n\n", string(message))
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleHealth 处理健康检查请求
|
||||
func handleHealth(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("OK"))
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 设置slog日志,仅输出到控制台,不写入文件
|
||||
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
||||
slog.SetDefault(logger)
|
||||
|
||||
logger.Info("MCP Time Server 启动")
|
||||
|
||||
// 创建SSE管理器
|
||||
sseManager := NewSSEManager()
|
||||
|
||||
// 创建路由器
|
||||
r := http.NewServeMux()
|
||||
|
||||
// 注册健康检查端点
|
||||
r.HandleFunc("/health", handleHealth)
|
||||
|
||||
// 注册MCP提交端点
|
||||
r.HandleFunc("/mcp/v1/submit", handleSubmit)
|
||||
|
||||
// 注册SSE端点
|
||||
r.HandleFunc("/sse", func(w http.ResponseWriter, r *http.Request) {
|
||||
handleSSE(w, r, sseManager)
|
||||
})
|
||||
|
||||
// 创建HTTP服务器
|
||||
srv := &http.Server{
|
||||
Addr: ":8080",
|
||||
Handler: r,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
}
|
||||
|
||||
// 启动服务器
|
||||
go func() {
|
||||
logger.Info("MCP Time Server 启动成功", "address", ":8080")
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
logger.Error("服务器启动失败", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待中断信号
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
|
||||
logger.Info("MCP Time Server 正在关闭...")
|
||||
|
||||
// 创建超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 优雅关闭服务器
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
logger.Error("服务器关闭失败", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logger.Info("MCP Time Server 已安全关闭")
|
||||
}
|
||||
BIN
mcpTimeServer/mcp_client_example
Executable file
BIN
mcpTimeServer/mcp_client_example
Executable file
Binary file not shown.
190
mcpTimeServer/mcp_client_example.go
Normal file
190
mcpTimeServer/mcp_client_example.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 这里使用通用的map类型表示请求和响应,避免与main.go中的结构体冲突
|
||||
|
||||
func main() {
|
||||
// 服务器地址
|
||||
serverURL := "http://localhost:8080"
|
||||
|
||||
// 1. 测试健康检查
|
||||
testHealthCheck(serverURL)
|
||||
|
||||
// 2. 测试获取当前时间(默认格式)
|
||||
testGetCurrentTime(serverURL, "")
|
||||
|
||||
// 3. 测试获取当前时间(自定义格式)
|
||||
testGetCurrentTime(serverURL, "2006-01-02 15:04:05")
|
||||
|
||||
// 4. 测试订阅时间流
|
||||
testSubscribeTimeStream(serverURL)
|
||||
}
|
||||
|
||||
// 测试健康检查
|
||||
func testHealthCheck(serverURL string) {
|
||||
url := fmt.Sprintf("%s/health", serverURL)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
fmt.Printf("健康检查请求失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
fmt.Println("✅ 健康检查成功: 服务器正常运行")
|
||||
} else {
|
||||
fmt.Printf("❌ 健康检查失败: 状态码 %d\n", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// 测试获取当前时间
|
||||
func testGetCurrentTime(serverURL string, format string) {
|
||||
url := fmt.Sprintf("%s/mcp/v1/submit", serverURL)
|
||||
|
||||
// 构建请求体
|
||||
requestBody := map[string]interface{}{
|
||||
"data": map[string]interface{}{},
|
||||
"type": "get_current_time",
|
||||
"timestamp": time.Now().Unix(),
|
||||
}
|
||||
|
||||
// 添加格式参数(如果提供)
|
||||
if format != "" {
|
||||
data := requestBody["data"].(map[string]interface{})
|
||||
data["format"] = format
|
||||
}
|
||||
|
||||
// 序列化请求体
|
||||
jsonData, err := json.Marshal(requestBody)
|
||||
if err != nil {
|
||||
fmt.Printf("序列化请求体失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 发送POST请求
|
||||
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
fmt.Printf("发送请求失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Printf("读取响应失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析响应为map
|
||||
var response map[string]interface{}
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
fmt.Printf("解析响应失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查响应状态
|
||||
success, ok := response["success"].(bool)
|
||||
if !ok || !success {
|
||||
message := "未知错误"
|
||||
if msg, ok := response["message"].(string); ok {
|
||||
message = msg
|
||||
}
|
||||
fmt.Printf("❌ 获取当前时间失败: %s\n", message)
|
||||
return
|
||||
}
|
||||
|
||||
// 提取数据
|
||||
data, ok := response["data"].(map[string]interface{})
|
||||
if !ok {
|
||||
fmt.Println("❌ 响应数据格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取时间信息
|
||||
currentTime, _ := data["current_time"].(string)
|
||||
timestamp, _ := data["timestamp"].(float64)
|
||||
fmt.Printf("✅ 获取当前时间成功 (格式: %s):\n", format)
|
||||
fmt.Printf(" 时间: %s\n", currentTime)
|
||||
fmt.Printf(" 时间戳: %.0f\n", timestamp)
|
||||
}
|
||||
|
||||
// 测试订阅时间流
|
||||
func testSubscribeTimeStream(serverURL string) {
|
||||
url := fmt.Sprintf("%s/mcp/v1/submit", serverURL)
|
||||
|
||||
// 构建请求体
|
||||
requestBody := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"interval": 2, // 每2秒更新一次
|
||||
"format": "2006-01-02 15:04:05",
|
||||
},
|
||||
"type": "subscribe_time_stream",
|
||||
"timestamp": time.Now().Unix(),
|
||||
}
|
||||
|
||||
// 序列化请求体
|
||||
jsonData, err := json.Marshal(requestBody)
|
||||
if err != nil {
|
||||
fmt.Printf("序列化请求体失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 发送POST请求
|
||||
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
fmt.Printf("发送请求失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Printf("读取响应失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析响应为map
|
||||
var response map[string]interface{}
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
fmt.Printf("解析响应失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查响应状态
|
||||
success, ok := response["success"].(bool)
|
||||
if !ok || !success {
|
||||
message := "未知错误"
|
||||
if msg, ok := response["message"].(string); ok {
|
||||
message = msg
|
||||
}
|
||||
fmt.Printf("❌ 订阅时间流失败: %s\n", message)
|
||||
return
|
||||
}
|
||||
|
||||
// 提取数据
|
||||
data, ok := response["data"].(map[string]interface{})
|
||||
if !ok {
|
||||
fmt.Println("❌ 响应数据格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取流信息
|
||||
streamID, _ := data["stream_id"].(string)
|
||||
sseURL, _ := data["sse_url"].(string)
|
||||
interval, _ := data["interval"].(float64)
|
||||
fmt.Printf("✅ 订阅时间流成功:\n")
|
||||
fmt.Printf(" 流ID: %s\n", streamID)
|
||||
fmt.Printf(" SSE URL: %s\n", sseURL)
|
||||
fmt.Printf(" 更新间隔: %.0f秒\n", interval)
|
||||
fmt.Println(" 提示: 可以使用curl或浏览器访问SSE URL来接收实时时间更新")
|
||||
}
|
||||
BIN
mcpTimeServer/mcp_time_server
Executable file
BIN
mcpTimeServer/mcp_time_server
Executable file
Binary file not shown.
Reference in New Issue
Block a user