Skip to content

Commit 8ffa621

Browse files
authored
Merge pull request #32 from aice030/main
fix(elasticsearch mcp):完善elasticsearch mcp工具的功能。实现通过配置文件查询不同实例
2 parents e42b8eb + cf138da commit 8ffa621

File tree

6 files changed

+276
-22
lines changed

6 files changed

+276
-22
lines changed

mcp-server/configs/config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,13 @@ superset:
2121
elasticsearch:
2222
port: 8092
2323
endpoint: "/es"
24+
# 索引配置
25+
index:
26+
pattern: "mock-{service}-logs-{date}" # 索引模式,支持{service}和{date}占位符
27+
date_format: "2006.01.02" # 日期格式,Go时间格式
28+
wildcard_pattern: "mock-*-logs-{date}" # 通配符模式,用于跨服务查询
29+
# 连接配置
30+
connection:
31+
base_url: "http://127.0.0.1:9200" # Elasticsearch基础URL
32+
timeout: 30 # 请求超时时间(秒)
33+
max_retries: 3 # 最大重试次数
Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,61 @@
11
package internal
22

3+
import (
4+
"time"
5+
6+
"qiniu1024-mcp-server/pkg/common/config"
7+
)
8+
9+
// ElasticsearchClient Elasticsearch客户端
310
type ElasticsearchClient struct {
4-
BaseURL string
11+
BaseURL string // Elasticsearch基础URL
12+
Timeout time.Duration // 请求超时时间
13+
MaxRetries int // 最大重试次数
14+
IndexManager *IndexManager // 索引管理器
515
}
616

17+
// NewElasticsearchClient 创建Elasticsearch客户端
718
func NewElasticsearchClient() (*ElasticsearchClient, error) {
819
// 从配置文件获取Elasticsearch配置
9-
// 这里可以根据需要添加具体的配置项
20+
esConfig := config.GlobalConfig.ElasticSearch.Connection
21+
22+
// 设置默认值
23+
baseURL := esConfig.BaseURL
24+
if baseURL == "" {
25+
baseURL = "http://localhost:9200"
26+
}
27+
28+
timeout := time.Duration(esConfig.Timeout) * time.Second
29+
if timeout == 0 {
30+
timeout = 30 * time.Second
31+
}
32+
33+
maxRetries := esConfig.MaxRetries
34+
if maxRetries == 0 {
35+
maxRetries = 3
36+
}
37+
38+
// 创建索引管理器
39+
indexManager := NewIndexManager()
40+
1041
return &ElasticsearchClient{
11-
BaseURL: "http://localhost:9200", // 默认地址,可以从配置文件读取
42+
BaseURL: baseURL,
43+
Timeout: timeout,
44+
MaxRetries: maxRetries,
45+
IndexManager: indexManager,
1246
}, nil
1347
}
48+
49+
// GetIndexManager 获取索引管理器
50+
func (e *ElasticsearchClient) GetIndexManager() *IndexManager {
51+
return e.IndexManager
52+
}
53+
54+
// GetConnectionConfig 获取连接配置信息
55+
func (e *ElasticsearchClient) GetConnectionConfig() map[string]interface{} {
56+
return map[string]interface{}{
57+
"base_url": e.BaseURL,
58+
"timeout": e.Timeout.String(),
59+
"max_retries": e.MaxRetries,
60+
}
61+
}

mcp-server/elasticsearch/internal/fetch.go

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,27 @@ package internal
33
import (
44
"encoding/json"
55
"fmt"
6+
"io"
67
"net/http"
78
"strings"
8-
"time"
99
)
1010

1111
// GetServiceByHostID 根据host_id获取对应的service名称
1212
// hostID: 主机ID
1313
// startTime: 开始时间,格式为 "2025-08-20T00:00:00Z"
1414
// endTime: 结束时间,格式为 "2025-08-20T23:59:59Z"
1515
func (e *ElasticsearchClient) GetServiceByHostID(hostID string, startTime string, endTime string) (string, error) {
16-
// 使用当前日期构建索引模式,查询所有服务
17-
currentDate := time.Now().Format("2006.01.02")
18-
indexPattern := fmt.Sprintf("mock-*-logs-%s", currentDate)
16+
// 根据查询时间范围生成索引模式,而不是使用当前日期
17+
indexPatterns, err := e.IndexManager.GenerateIndexPatternsForTimeRange(startTime, endTime, "")
18+
if err != nil {
19+
return "", fmt.Errorf("生成索引模式失败: %w", err)
20+
}
21+
22+
// 使用第一个索引模式进行查询(简化处理)
23+
if len(indexPatterns) == 0 {
24+
return "", fmt.Errorf("未生成有效的索引模式")
25+
}
26+
indexPattern := indexPatterns[0]
1927

2028
// 构建查询,根据host_id查找对应的service
2129
query := map[string]interface{}{
@@ -88,11 +96,17 @@ func (e *ElasticsearchClient) FetchLogsByServiceAndHost(service string, hostID s
8896
}
8997
}
9098

91-
// 构建索引名称,格式:mock-服务名-logs-日期
92-
// 这里需要根据时间范围生成对应的索引名称
93-
// 简化处理:使用当前日期作为示例
94-
currentDate := time.Now().Format("2006.01.02")
95-
indexName := fmt.Sprintf("mock-%s-logs-%s", service, currentDate)
99+
// 根据时间范围生成具体的索引名
100+
indexPatterns, err := e.IndexManager.GenerateIndexPatternsForTimeRange(startTime, endTime, service)
101+
if err != nil {
102+
return "", fmt.Errorf("生成索引模式失败: %w", err)
103+
}
104+
105+
// 使用第一个索引模式
106+
if len(indexPatterns) == 0 {
107+
return "", fmt.Errorf("未生成有效的索引模式")
108+
}
109+
indexName := indexPatterns[0]
96110

97111
// 构建Elasticsearch查询
98112
query := map[string]interface{}{
@@ -161,9 +175,17 @@ func (e *ElasticsearchClient) FetchLogsByServiceAndHost(service string, hostID s
161175
// startTime: 开始时间,格式为 "2025-08-20T00:00:00Z"
162176
// endTime: 结束时间,格式为 "2025-08-20T23:59:59Z"
163177
func (e *ElasticsearchClient) FetchRequestTrace(requestID string, startTime string, endTime string) (string, error) {
164-
// 由于请求可能经过多个服务,需要查询所有相关的索引
165-
// 这里简化处理,查询当前日期的所有服务索引
166-
currentDate := time.Now().Format("2006.01.02")
178+
// 根据查询时间范围生成索引模式,查询所有服务的索引
179+
indexPatterns, err := e.IndexManager.GenerateIndexPatternsForTimeRange(startTime, endTime, "")
180+
if err != nil {
181+
return "", fmt.Errorf("生成索引模式失败: %w", err)
182+
}
183+
184+
// 使用第一个索引模式进行查询(简化处理)
185+
if len(indexPatterns) == 0 {
186+
return "", fmt.Errorf("未生成有效的索引模式")
187+
}
188+
indexPattern := indexPatterns[0]
167189

168190
// 构建跨索引查询
169191
query := map[string]interface{}{
@@ -200,7 +222,6 @@ func (e *ElasticsearchClient) FetchRequestTrace(requestID string, startTime stri
200222
}
201223

202224
// 执行跨索引查询
203-
indexPattern := fmt.Sprintf("mock-*-logs-%s", currentDate)
204225
result, err := e.executeSearch(indexPattern, searchRequest)
205226
if err != nil {
206227
return "", fmt.Errorf("查询请求追踪失败: %w", err)
@@ -268,23 +289,28 @@ func (e *ElasticsearchClient) executeSearch(index string, searchRequest map[stri
268289
req.Header.Set("Content-Type", "application/json")
269290

270291
// 发送请求
271-
client := &http.Client{Timeout: 30 * time.Second}
292+
client := &http.Client{Timeout: e.Timeout}
272293
resp, err := client.Do(req)
273294
if err != nil {
274295
return nil, fmt.Errorf("发送HTTP请求失败: %w", err)
275296
}
276297
defer resp.Body.Close()
277298

299+
// 读取响应体
300+
bodyBytes, err := io.ReadAll(resp.Body)
301+
if err != nil {
302+
return nil, fmt.Errorf("读取响应体失败: %w", err)
303+
}
304+
278305
// 解析响应
279306
var result map[string]interface{}
280-
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
307+
if err := json.Unmarshal(bodyBytes, &result); err != nil {
281308
return nil, fmt.Errorf("解析响应失败: %w", err)
282309
}
283310

284311
// 检查是否有错误
285312
if resp.StatusCode != http.StatusOK {
286-
return nil, fmt.Errorf("Elasticsearch返回错误状态码: %d", resp.StatusCode)
313+
return nil, fmt.Errorf("Elasticsearch返回错误状态码: %d, 响应: %s", resp.StatusCode, string(bodyBytes))
287314
}
288-
289315
return result, nil
290316
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package internal
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"time"
7+
8+
"qiniu1024-mcp-server/pkg/common/config"
9+
)
10+
11+
// IndexManager Elasticsearch索引管理器
12+
type IndexManager struct {
13+
pattern string // 索引模式
14+
dateFormat string // 日期格式
15+
wildcardPattern string // 通配符模式
16+
}
17+
18+
// NewIndexManager 创建索引管理器
19+
func NewIndexManager() *IndexManager {
20+
// 从全局配置获取索引配置
21+
esConfig := config.GlobalConfig.ElasticSearch.Index
22+
23+
// 设置默认值
24+
pattern := esConfig.Pattern
25+
if pattern == "" {
26+
pattern = "mock-{service}-logs-{date}"
27+
}
28+
29+
dateFormat := esConfig.DateFormat
30+
if dateFormat == "" {
31+
dateFormat = "2006.01.02"
32+
}
33+
34+
wildcardPattern := esConfig.WildcardPattern
35+
if wildcardPattern == "" {
36+
wildcardPattern = "mock-*-logs-{date}"
37+
}
38+
39+
return &IndexManager{
40+
pattern: pattern,
41+
dateFormat: dateFormat,
42+
wildcardPattern: wildcardPattern,
43+
}
44+
}
45+
46+
// GenerateIndexName 生成具体的索引名称
47+
// service: 服务名称
48+
// date: 日期,如果为空则使用当前日期
49+
func (im *IndexManager) GenerateIndexName(service string, date time.Time) string {
50+
if date.IsZero() {
51+
date = time.Now()
52+
}
53+
54+
dateStr := date.Format(im.dateFormat)
55+
56+
// 替换模式中的占位符
57+
indexName := im.pattern
58+
indexName = strings.ReplaceAll(indexName, "{service}", service)
59+
indexName = strings.ReplaceAll(indexName, "{date}", dateStr)
60+
61+
return indexName
62+
}
63+
64+
// GenerateWildcardPattern 生成通配符模式
65+
// date: 日期,如果为空则使用当前日期
66+
func (im *IndexManager) GenerateWildcardPattern(date time.Time) string {
67+
if date.IsZero() {
68+
date = time.Now()
69+
}
70+
71+
dateStr := date.Format(im.dateFormat)
72+
73+
// 替换模式中的占位符
74+
pattern := im.wildcardPattern
75+
pattern = strings.ReplaceAll(pattern, "{date}", dateStr)
76+
77+
return pattern
78+
}
79+
80+
// GenerateIndexPatternsForDateRange 为日期范围生成索引模式列表
81+
// startDate: 开始日期
82+
// endDate: 结束日期
83+
// service: 服务名称,如果为空则使用通配符
84+
func (im *IndexManager) GenerateIndexPatternsForDateRange(startDate, endDate time.Time, service string) []string {
85+
var patterns []string
86+
87+
// 计算日期范围内的所有日期
88+
currentDate := startDate
89+
for !currentDate.After(endDate) {
90+
if service == "" {
91+
// 使用通配符模式
92+
pattern := im.GenerateWildcardPattern(currentDate)
93+
patterns = append(patterns, pattern)
94+
} else {
95+
// 使用具体服务模式
96+
indexName := im.GenerateIndexName(service, currentDate)
97+
patterns = append(patterns, indexName)
98+
}
99+
100+
// 移动到下一天
101+
currentDate = currentDate.AddDate(0, 0, 1)
102+
}
103+
104+
return patterns
105+
}
106+
107+
// GenerateIndexPatternsForTimeRange 为时间范围生成索引模式列表
108+
// startTime: 开始时间字符串,格式为 "2025-08-20T00:00:00Z"
109+
// endTime: 结束时间字符串,格式为 "2025-08-20T23:59:59Z"
110+
// service: 服务名称,如果为空则使用通配符
111+
func (im *IndexManager) GenerateIndexPatternsForTimeRange(startTime, endTime string, service string) ([]string, error) {
112+
// 解析时间字符串
113+
startDate, err := time.Parse(time.RFC3339, startTime)
114+
if err != nil {
115+
return nil, fmt.Errorf("解析开始时间失败: %w", err)
116+
}
117+
118+
endDate, err := time.Parse(time.RFC3339, endTime)
119+
if err != nil {
120+
return nil, fmt.Errorf("解析结束时间失败: %w", err)
121+
}
122+
123+
// 提取日期部分(去掉时间)
124+
startDate = time.Date(startDate.Year(), startDate.Month(), startDate.Day(), 0, 0, 0, 0, startDate.Location())
125+
endDate = time.Date(endDate.Year(), endDate.Month(), endDate.Day(), 0, 0, 0, 0, endDate.Location())
126+
127+
return im.GenerateIndexPatternsForDateRange(startDate, endDate, service), nil
128+
}
129+
130+
// GetCurrentIndexPattern 获取当前日期的索引模式
131+
// service: 服务名称,如果为空则使用通配符
132+
func (im *IndexManager) GetCurrentIndexPattern(service string) string {
133+
if service == "" {
134+
return im.GenerateWildcardPattern(time.Time{}) // 使用当前日期
135+
}
136+
return im.GenerateIndexName(service, time.Time{}) // 使用当前日期
137+
}
138+
139+
// ValidateIndexPattern 验证索引模式是否有效
140+
func (im *IndexManager) ValidateIndexPattern(pattern string) error {
141+
if pattern == "" {
142+
return fmt.Errorf("索引模式不能为空")
143+
}
144+
145+
// 检查是否包含必要的占位符
146+
if !strings.Contains(pattern, "{date}") {
147+
return fmt.Errorf("索引模式必须包含{date}占位符")
148+
}
149+
150+
return nil
151+
}
152+
153+
// GetIndexConfig 获取索引配置信息
154+
func (im *IndexManager) GetIndexConfig() map[string]interface{} {
155+
return map[string]interface{}{
156+
"pattern": im.pattern,
157+
"date_format": im.dateFormat,
158+
"wildcard_pattern": im.wildcardPattern,
159+
}
160+
}

mcp-server/elasticsearch/internal/tools.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ func GetServiceByHostIDTool() mcp.Tool {
1212
mcp.Description("主机ID,用于定位具体的服务器节点")),
1313
mcp.WithString("start_time",
1414
mcp.Required(),
15-
mcp.Description("查询开始时间,格式为 '2025-08-20T00:00:00Z'")),
15+
mcp.Description("查询开始时间,需要北京时间,格式为 'YYYY-mm-ddThh:mm:ss+08:00'")),
1616
mcp.WithString("end_time",
1717
mcp.Required(),
18-
mcp.Description("查询结束时间,格式为 '2025-08-20T23:59:59Z'")),
18+
mcp.Description("查询结束时间,需要北京时间,格式为 'YYYY-mm-ddThh:mm:ss+0800'")),
1919
)
2020
}
2121

mcp-server/pkg/common/config/config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ type Config struct {
3030
ElasticSearch struct {
3131
Port int `yaml:"port"`
3232
Endpoint string `yaml:"endpoint"`
33+
Index struct {
34+
Pattern string `yaml:"pattern"` // 索引模式,支持{service}和{date}占位符
35+
DateFormat string `yaml:"date_format"` // 日期格式,Go时间格式
36+
WildcardPattern string `yaml:"wildcard_pattern"` // 通配符模式,用于跨服务查询
37+
} `yaml:"index"`
38+
Connection struct {
39+
BaseURL string `yaml:"base_url"` // Elasticsearch基础URL
40+
Timeout int `yaml:"timeout"` // 请求超时时间(秒)
41+
MaxRetries int `yaml:"max_retries"` // 最大重试次数
42+
} `yaml:"connection"`
3343
} `yaml:"elasticsearch"`
3444
}
3545

0 commit comments

Comments
 (0)