Skip to content

Commit 7ec96eb

Browse files
committed
feat(mongodb): mongodb_mcp TencentBlueKing#16073
1 parent c429238 commit 7ec96eb

28 files changed

+1191
-104
lines changed

dbm-ui/backend/dbm_aiagent/mcp_tools/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ python manage.py sync_saas_apigw --mcp
177177

178178
1. 修改 mcp 代码后, 执行 `python manage.py sync_saas_apigw --only_mcp_resource` 更新本地描述文件
179179
2. `export DEBUG_MCP=1; python manage.py runserver appdev.aaa.bbb.com:8080` 以调试模式启动 django 服务, server url 没啥要求, 随便
180-
3. `build/db-mcp-server --bind-address 0.0.0.0:9191 --mcp-backend-base-url http://appdev.aaa.bbb.com:8080/` 启动 mcp-server
180+
3. `build/db-mcp-server --bind-address 0.0.0.0:9191 --mcp-backend-base-url http://appdev.paasdb.woa.com:8080/` 启动 mcp-server
181181
4. 如果想本地启动 bkai agent, 参考其他文档
182182

183183
### 联调配置

dbm-ui/backend/dbm_aiagent/mcp_tools/constants.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,18 @@ class DBMMcpTools(StrStructuredEnum):
3434
REDIS_QUERY_ALARM = EnumField("redis-query-alarm", "redis-query-alarm")
3535
REDIS_BILL = EnumField("redis-bill", "redis-bill")
3636
REDIS_JOB = EnumField("redis-job", "redis-job")
37-
MONGODB_QUERY_META = EnumField("mongodb-query-meta", "mongodb-query-meta")
38-
MONGODB_QUERY_STATUS = EnumField("mongodb-query-status", "mongodb-query-status")
39-
MONGODB_QUERY_LOG = EnumField("mongodb-query-log", "mongodb-query-log")
40-
MONGODB_QUERY_ALARM = EnumField("mongodb-query-alarm", "mongodb-query-alarm")
37+
MONGODB_META = EnumField("mongodb-meta", "mongodb-meta")
38+
MONGODB_SYSTEM = EnumField("mongodb-system", "mongodb-system")
39+
MONGODB_METRICS = EnumField("mongodb-metrics", "mongodb-metrics")
4140
MONGODB_BILL = EnumField("mongodb-bill", "mongodb-bill")
4241
MONGODB_JOB = EnumField("mongodb-job", "mongodb-job")
42+
MONGODB_LOG = EnumField("mongodb-log", "mongodb-log")
43+
MONGODB_ALARM = EnumField("mongodb-alarm", "mongodb-alarm")
44+
MONGODB_STATUS = EnumField("mongodb-status", "mongodb-status")
4345
KAFKA_QUERY_META = EnumField("kafka-query-meta", "kafka-query-meta")
4446
KAFKA_BILL = EnumField("kafka-bill", "kafka-bill")
4547

48+
4649
class DBMMCPTags(StrStructuredEnum):
4750
READ = EnumField("read", _("只读"))
4851
WRITE = EnumField("write", _("可写"))

dbm-ui/backend/dbm_aiagent/mcp_tools/decorators.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ def mcp_tools_api_decorator(
7777
match_subpath: bool = False,
7878
user_verified_required: bool = False,
7979
app_verified_required: bool = True,
80+
none_schema: bool = False,
8081
):
8182
"""
8283
MCP 工具 API 装饰器
@@ -96,6 +97,7 @@ def mcp_tools_api_decorator(
9697
@params match_subpath: 匹配所有子路径
9798
@params user_verified_required: 是否校验用户身份(考虑 mcp 也有后台调用,默认都已应用态接口开放)
9899
@params app_verified_required: 是否校验应用身份
100+
@params none_schema: 无请求体时设为 True,供 generate_resources_yaml 的 MCP 校验通过
99101
@returns 装饰器函数
100102
"""
101103

@@ -129,6 +131,7 @@ def decorator(func: Callable) -> Callable:
129131
exclude=False,
130132
extensions=gen_apigateway_resource_config(
131133
enable_mcp=True, # 固定为 True
134+
none_schema=none_schema,
132135
is_public=is_public,
133136
allow_apply_permission=allow_apply_permission,
134137
user_verified_required=user_verified_required,

dbm-ui/backend/dbm_aiagent/mcp_tools/mongodb/impl/cluster_meta.py

Lines changed: 127 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,58 @@
77
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
88
specific language governing permissions and limitations under the License.
99
"""
10+
import copy
11+
import logging
12+
import re
13+
import time
1014
from collections import defaultdict
1115
from typing import Dict, List
1216

1317
from django.db.models import F
1418

19+
from backend import env
20+
from backend.components import BKMonitorV3Api
1521
from backend.configuration.constants import DBType
1622
from backend.configuration.models import DBAdministrator
1723
from backend.db_meta.enums import ClusterType, MachineType
1824
from backend.db_meta.models import AppCache, Cluster, ClusterEntry, Machine, ProxyInstance, StorageInstance
1925

26+
logger = logging.getLogger("root")
27+
28+
# 用于从 TS 指标查询 meta 的 unify_query 模板(instant)
29+
_UNIFY_QUERY_META_PARAMS = {
30+
"bk_biz_id": 3,
31+
"query_configs": [
32+
{
33+
"data_source_label": "prometheus",
34+
"data_type_label": "time_series",
35+
"promql": "",
36+
"interval": 60,
37+
"alias": "a",
38+
}
39+
],
40+
"expression": "a",
41+
"alias": "a",
42+
"start_time": 0,
43+
"end_time": 0,
44+
"slimit": 500,
45+
"type": "instant",
46+
}
47+
2048

2149
def list_my_mongodb_bizs(username: str) -> List:
2250
res = []
23-
for app in AppCache.objects.all():
51+
for app in AppCache.objects.all(): # pyright: ignore[reportAttributeAccessIssue]
2452
bk_biz_id = app.bk_biz_id
25-
if DBAdministrator.objects.filter(
53+
if DBAdministrator.objects.filter( # pyright: ignore[reportAttributeAccessIssue]
2654
bk_biz_id=bk_biz_id, users__0=username, db_type=DBType.MongoDB.value
2755
).exists():
2856
res.append({"bk_biz_id": bk_biz_id, "app_name": app.bk_biz_name, "abbr": app.db_app_abbr})
2957
return res
3058

3159

3260
def mongodb_list_clusters(bk_biz_id: int) -> List:
33-
clusters = Cluster.objects.filter(
61+
clusters = Cluster.objects.filter( # pyright: ignore[reportAttributeAccessIssue]
3462
bk_biz_id=bk_biz_id,
3563
cluster_type__in=[ClusterType.MongoReplicaSet, ClusterType.MongoShardedCluster],
3664
)
@@ -88,8 +116,103 @@ def get_machine_stats(all_machine_ids) -> Dict:
88116
return machine_distribution
89117

90118

119+
def get_mongodb_meta_from_ts_metric(conds: Dict) -> Dict:
120+
"""
121+
从监控指标 bkmonitor:dbm_system:cpu_summary:usage 的 label 中解析出 (cluster_domain, bk_target_ip, instance_role),
122+
再用 DBM 元数据补全 cluster_name、cluster_id、port、bk_biz_id、app_name、shard 等。
123+
124+
conds 支持:
125+
- cluster_domain: 集群域名,仅查询该集群
126+
- ip: 主机 IP,仅查询该 IP 上的实例
127+
- instance_port: 可选,与 ip 一起时过滤指定端口(通过 DBM 元数据过滤)
128+
"""
129+
# mongodb_types = [ClusterType.MongoReplicaSet.value, ClusterType.MongoShardedCluster.value]
130+
group_by_keys = [
131+
"cluster_domain", "bk_target_ip", "instance_role", "shard", "instance_port",
132+
"cluster_type", "instance_host", "instance",
133+
]
134+
label_filters = []
135+
if conds.get("cluster_domain"):
136+
label_filters.append(f'cluster_domain="{conds["cluster_domain"]}"')
137+
if conds.get("ip"):
138+
label_filters.append(f'bk_target_ip="{conds["ip"]}"')
139+
label_str = ",".join(label_filters) if label_filters else ""
140+
promql = (
141+
f'max by ({",".join(group_by_keys)}) '
142+
f'(max_over_time(bkmonitor:dbm_system:cpu_summary:usage{{{label_str}}}[5m]))'
143+
)
144+
params = copy.deepcopy(_UNIFY_QUERY_META_PARAMS)
145+
params["bk_biz_id"] = env.DBA_APP_BK_BIZ_ID
146+
end_ts = int(time.time())
147+
params["start_time"] = end_ts - 300
148+
params["end_time"] = end_ts
149+
params["query_configs"][0]["promql"] = promql
150+
151+
try:
152+
resp = BKMonitorV3Api.unify_query(params)
153+
except Exception as e:
154+
logger.exception("get_mongodb_meta_from_ts_metric unify_query error: %s", e)
155+
return {"meta_list": [], "error": str(e)}
156+
157+
series = resp.get("series", [])
158+
meta_list = []
159+
160+
for s in series:
161+
dims = s.get("dimensions") or s.get("group_keys") or s.get("metric") or {}
162+
cluster_domain = dims.get("cluster_domain")
163+
bk_target_ip = dims.get("bk_target_ip")
164+
instance_role = dims.get("instance_role", "")
165+
instance_port = dims.get("instance_port", "")
166+
instance = dims.get("instance", "") or f"{bk_target_ip}:{instance_port}"
167+
instance_host = dims.get("instance_host", "") or f"{bk_target_ip}"
168+
cluster_type = dims.get("cluster_type", "")
169+
shard = dims.get("shard", "")
170+
meta_list.append({
171+
"cluster_domain": cluster_domain,
172+
"cluster_type": cluster_type,
173+
"bk_target_ip": bk_target_ip,
174+
"instance_port": instance_port,
175+
"instance_role": instance_role,
176+
"instance_host": instance_host,
177+
"instance": instance,
178+
"shard": shard,
179+
})
180+
181+
return {"meta_list": meta_list, "error": ""}
182+
183+
184+
def meta_info(value: str) -> Dict:
185+
"""
186+
根据输入的值,返回对应的元信息(从 TS 指标 bkmonitor:dbm_system:cpu_summary:usage 的 label 解析,再以 DBM 元数据补全)。
187+
188+
value 支持:
189+
- IP:如 "11.177.156.201"
190+
- IP:PORT:如 "11.177.156.201:50005"
191+
- 集群域名:如 "mongo.xxx.db"(含点号)
192+
"""
193+
try:
194+
conds = {}
195+
if value is None:
196+
return {"meta_list": [], "error": "value is None"}
197+
value = (value or "").strip()
198+
if re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", value):
199+
conds["ip"] = value
200+
elif re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}$", value):
201+
ip, port = value.split(":", 1)
202+
conds["instance"] = f"{ip}:{port}"
203+
elif "." in value:
204+
conds["cluster_domain"] = value
205+
else:
206+
return {"meta_list": [], "error": "Invalid value"}
207+
return get_mongodb_meta_from_ts_metric(conds)
208+
except Exception as e:
209+
return {"meta_list": [], "error": f"查询 MongoDB 实例的元数据信息时出错: {str(e)}"}
210+
211+
91212
def cluster_overview(immute_domain: str) -> Dict:
92-
cluster_obj = Cluster.objects.prefetch_related("tags").get(immute_domain=immute_domain)
213+
cluster_obj = Cluster.objects.prefetch_related("tags").get( # pyright: ignore[reportAttributeAccessIssue]
214+
immute_domain=immute_domain
215+
)
93216
stats = {
94217
"bk_cloud_id": cluster_obj.bk_cloud_id,
95218
"bk_biz_id": cluster_obj.bk_biz_id,

dbm-ui/backend/dbm_aiagent/mcp_tools/mongodb/impl/get_source_access_impl.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@
88
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
99
specific language governing permissions and limitations under the License.
1010
"""
11-
from itertools import chain
12-
13-
from backend.db_meta.enums import ClusterType
14-
from backend.db_meta.models import Cluster
1511
from backend.dbm_aiagent.mcp_tools.redis.impl.get_source_access_impl import generate_cluster_query_report
1612

1713

dbm-ui/backend/dbm_aiagent/mcp_tools/mongodb/impl/mongodb_alarms.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"""
1010
import time
1111
from datetime import datetime
12-
from typing import Dict, List, Optional, Union
12+
from typing import Any, Dict, List, Optional, Union, cast
1313

1414
from backend import env
1515
from backend.components import BKMonitorV3Api
@@ -22,7 +22,10 @@ def get_cluster_alarms(
2222
end_time: Optional[Union[int, str, datetime]] = None,
2323
) -> Dict:
2424
alarms = get_alarms_flat(immute_domain=immute_domain, start_time=start_time, end_time=end_time)
25-
by_alert = alarms.get(immute_domain, {})
25+
by_alert = cast(
26+
Dict[str, List[Any]],
27+
alarms.get(immute_domain, {}) if immute_domain is not None else {},
28+
)
2629
cluster_alarms = [{"alert_name": k, "alert_detail": v} for k, v in by_alert.items()]
2730
total = sum(len(v) for v in by_alert.values())
2831
return {"total_alarms": total, "alarm_detail": cluster_alarms}

0 commit comments

Comments
 (0)