**MCP for 可观测2.0 —— 6个用好MCP的实践**
**MCP 简述**
MCP 是一种开放协议,用于标准化应用程序如何向 LLM 提供上下文。可以将 MCP 视为 AI 应用程序的 USB-C 端口。就像 USB-C 提供了一种将设备连接到各种外围设备和配件的标准化方式一样,MCP 提供了一种将 AI 模型连接到不同数据源和工具的标准化方式。

- MCP 主机 (MCP Hosts):例如 Claude Desktop、IDE 或希望通过 MCP 访问数据的 AI 工具等程序
- MCP 客户端 (MCP Clients):与服务器保持 1:1 连接的协议客户端
- MCP 服务器 (MCP Servers):轻量级程序,每个程序通过标准化的模型上下文协议 (MCP) 公开特定的功能
- 本地数据源 (Local Data Sources):您的计算机的文件、数据库和 MCP 服务器可以安全访问的服务
- 远程服务 (Remote Services):可通过互联网访问的外部系统(例如,通过 API),MCP 服务器可以连接到这些系统
**快速开始**
把MCP框架理解为“大模型插件”,并且你也可以很方便地编写自己的“插件”功能。
- [必须] 首先你需要一个支持MCP的Client,参考下一章节。在本文中,使用的是 Cherry Studio 和DeepChat Client。
- [必须] 需要准备LLM API。准备好api key。在本文中,使用的是阿里云百炼。如果没有api key,也可以使用本地化大模型(ollama)本地化相关功能参考:https://ata.atatech.org/articles/11020220022

- [可选] 按需添加 MCP Server,参考下两章节。
- [可选] 编写属于自己的 MCP Server。在https://github.com/modelcontextprotocol仓库下,选择您期望的SDK。添加自定义功能的Tools,就可以启动Server,供大模型调用了。
配置如下:


**Awesome MCP Clients 推荐**
完整列表参考:https://github.com/punkpeye/awesome-mcp-clients
对比参考:https://modelcontextprotocol.io/clients
笔者推荐:
- Cherry Studio

- DeepChat

- AIaW (注意内部数据安全)

- Cursor
- Continue
**Awesome MCP Servers 推荐**
完整列表参考:https://github.com/punkpeye/awesome-mcp-servers
文档实例列表:https://modelcontextprotocol.io/examples
官方列表:https://github.com/modelcontextprotocol/servers
按需添加即可。
**MCP Server for 阿里云可观测2.0**
**可观测2.0**
过去一年,我们迈向了通用可观测之路。从“监控”到“可观测”,不仅是技术升级,更是对复杂系统认知的深化。通过因果推理的三个层级(关联、干预、反事实),可观测性能够实现从被动观测到主动预测的能力提升。企业数字化需要将黑盒系统转化为白盒,通过采集、存储和分析多模态数据(如Log、Trace、Metric等),优化运营效率。
随着分布式系统和技术栈的复杂化,可观测性成为确保系统稳定性和性能的关键。然而,许多企业面临可观测数据混乱的问题,如缺乏标准化、数据存储分散、分析效率低及知识难以沉淀。为解决这些问题,我们提出了UModel——可观测数据之魂,一种通用的可观测“交互语言”。

UModel基于本体论思想,通过Set和Link组成的图模型描述IT世界,定义了EntitySet、LogSet、MetricSet等核心类型,以及EntitySetLink、DataLink等关联关系。它支持灵活扩展,引入CommonSchema降低使用门槛,并提供Explorer、告警与事件集等功能提升易用性。UModel不仅实现了数据的标准化和统一建模,还支持算法和大模型对数据的高效利用,助力构建全新的“可观测2.0”体系。
**融合 MCP 能力**


在可观测2.0中融合MCP能力,是一种很合适的尝试,一定程度上可以通过对话的方式,让用户对可观测2.0整体使用方式有更好的感知,并且可以协助用户感知系统、分析问题——只用自然语言交流。
**效果展示**
服务**全链路分析**
请至钉钉文档查看附件《CleanShot 2025-04-23 at 19.48.59.mp4》
注:此案例部分接口使用虚拟数据。
特性如下:
- 根据某服务,检索服务上下游、依赖的中间件和基础设施
- 分析某服务的指标情况
- 查询某服务是否存在错误请求trace id,并进一步智能分析
**可观测基础信息能力查询**
特性如下:
- 查询可观测2.0中多类信息:
- 获取可用区
- 查找workspace
- 查询entity 和 topo能力
- 查询 Umodel Schema 并生成图片展示。
**设计好MCP** Servers**的亲身实践(血泪踩坑)**
**经验一:Tools 接口精简化、原子化**
写在前面:MCP Server ≠ SDK API,是和人打交道的接口。
这里的“和人打交道”,是全方面的,无论是接口的理解,参数,还是返回,都要求简洁易懂。“三岁小孩都能懂”的MCP Server Tools才是好Tools。
举个例子🌰:
SLS 的 getLogs SDK API 是这样的
def get_logs(
ak: str,
sk: str,
region_id: str,
project: str,
logstore:str,
query: str,
from_timestamp: int,
to_timestamp: int,
topic: str,
line: int,
offset: int,
reverse: boolean,
powerSql: boolean
) -> list[Any]:
这个接口如果直接给到大模型作为Tools,可以说调用的成功率为0,因为这是一个非常复杂的接口:
- 主要的瓶颈在 query 怎么写,语法是怎样的,对模型挑战非常大。这种场景适合A2A(Agent协作),生成可靠的SLS query作为一个Agent,此处不过多展开。
- 假设我们解决了query的问题,API中的很多基础信息需要多轮对话获得:aksk、region、project、logstore,实际使用上这种环境变量级别的参数重复性非常高。
- 参数中带了时间窗口,这一点需要谨慎,详情见经验二。
- 参数中带了topic、line、offset、reverse、powerSql这种不常用参数,使用默认值能搞定大部分请求的参数就不带。
对于这些问题,在整体的实践后,认为在阿里云上,使用MCP Server和用户交互的架构,可能需要做一些改变。
朴素想法:


真正适用的:


首先,这里的MCP Server的生命周期应该是在用户进入到Workspace后,才创建。阿里云提供给用户的MCP交互 Client + MCP Server 是非常轻量的,一个用户、一次Workspace访问就是一次生命周期。而此时MCP Server是带上aksk、Workspace name、region id等环境变量信息的,里面的各种Tools无需关心这种基础的参数了。
然后,随着用户业务的范围,可以创建若干对应模块的Tools以供用户按需调用。
最后,需要把常用的操作封装MCP Tools,而不是给出一个万能的“query”接口。
| 原始接口 | 封装接口 |
|---|---|
| 1. 获取Workspace Schema(多种参数) 2. 查询实体、关系信息(Spl Query) | 1. 查询实体黄金指标 1. 输入是实体id,根据实体id查询实体信息 2. 根据实体类型,查询实体的黄金指标定义。 3. 根据实体属性和关键值信息,和黄金指标定义进行模板替换。 4. 查询metricstore,获得黄金指标信息,并返回。 2. 查询实体关联 1. 输入是实体id,根据实体类型查询实体Schema关联 2. 固定Cypher模式,查询实体关联1度的邻居节点id 3. 查询获取邻居节点们的实体信息,并返回。 |
因此,给出的getLogs,优化过后的MCP Server Tools版本应该是这样的:
A2A 模式:
def get_log_tool(
query: str,
from_timestamp: int = Field(
int(datetime.now().timestamp()) - 3600, description="from timestamp,unit is second"
),
to_timestamp: int = Field(
int(datetime.now().timestamp()), description="to timestamp,unit is second"
)
) -> list[Any]:
def gen_sls_query(
text: str,
) -> str:
功能模块化模式(仅示例):
获取logstore的index信息
def get_index():
pass
获得fields字段聚合的统计信息
def get_fields_desc(
filter: str,
fields: List[str],
from_timestamp: int = Field(
int(datetime.now().timestamp()) - 3600, description="from timestamp,unit is second"
),
to_timestamp: int = Field(
int(datetime.now().timestamp()), description="to timestamp,unit is second"
)
) -> list[Any]:
\* and upstream\_status >= 400 | SELECT request\_uri, upstream\_status, COUNT (\*) AS cnt
FROM log
GROUP BY request\_uri, upstream\_status
ORDER BY cnt DESC
LIMIT 10
统计UV
def get_uv(
filter: str,
field: str
from_timestamp: int = Field(
int(datetime.now().timestamp()) - 3600, description="from timestamp,unit is second"
),
to_timestamp: int = Field(
int(datetime.now().timestamp()), description="to timestamp,unit is second"
)
) -> list[Any]:
\* | SELECT COUNT(DISTINCT client\_ip) AS unique\_visitors FROM log
这里不需要ak、sk、project、logstore信息的原因是,这里的MCP应该是在logstore打开的时候启动的,因此已经知道基础环境信息了。
这样的功能模块划分仅为示例,仅作说明。
**经验二:Tools 接口参数默认化。慎用时间参数。**
案例:一个查询SQL Query的Tools,接口是这样的:
def o11y_list_entities(
ctx: Context,
query: str = Field(default=None, description="query"),
from_timestamp: int = Field(
..., description="from timestamp,unit is second"
),
to_timestamp: int = Field(
..., description="to timestamp,unit is second"
)
) -> list[str]:
为了方便大模型使用,特意增加了一个tool 用于调用时间相关的参数:

想法很好,现实很骨感,实际使用中,经常出现传入并不合法的 from to 的情况:

导致接口直接报错,因为传入不合法的参数,这种场景甚至必须重启client和Server。

有时也会传入离谱的时间参数,导致后续的回答并不理想:

时间戳这里提供的是 2023-06-25 00:00:00。大模型对“现在的时间”概念模糊。提供的示例对于一个只保存30天的LogStore来说仍然查不出数据。
更好的方式:
def o11y_list_entities(
ctx: Context,
query: str = Field(default=None, description="query"),
from_timestamp: int = Field(
int(datetime.now().timestamp()) - 3600, description="from timestamp,unit is second"
),
to_timestamp: int = Field(
int(datetime.now().timestamp()), description="to timestamp,unit is second"
),
) -> list[str]:
直接给出最近1小时的默认值,极大概率都可以覆盖期望的查询,只需考虑query的传入,不用纠结时间的细节设置。如果返回为空,模型会考虑是否是时间问题,并反馈。

**经验三:精简输出,避免过长的上下文输出**
一些不好的例子:查询workspace的list(返回500个workspace),查询某个workspace下接入的entity类型(返回200个)
MCP Server 和 SDK API 的功能不能完全等价看待,MCP 本质上是给人作为终端显示的,因此人眼看不过来(体感:大于20个的数量)都是意义不大的内容。每个接口的输出都应该控制limit。可以考虑通过加入筛选检索,或者直接截断输出到10个。
除了体感之外,过长的json response会严重卡慢后续模型的输出速度、影响上下文理解效果。
以上是一般json接口的经验。在实践中,我们还会遇到这类场景:希望Tools返回一幅图(以svg或png的形式)。尝试了几种方案:
- 直接返回 的data xml:client不会正常显示。且会因为返回数据较大而卡慢速度。
- 使用MCP SDK的Image 类,返回"url": f"data:image/png;base64,{tool_result.data}":多数client不会正常显示,且需要模型是多模态模型。
- 使用图片辅助服务,直接返回markdown的url格式:效果较好。
Args:
ctx: MCP上下文,用于访问可观测客户端
workspace_name: Workspace 名称,必须完全匹配Workspace
Returns:
Schema 的 markdown 内容,直接作为你的输出打印并展示


效果:

**经验四:避免Tools和Tools输入输出的链式传递**
好的实践:Tool原子能力,一个Tool做独立一件事。
不好的实践:Tool1 -> output -> Tool2 input -> output -> Tool3
实践中链式传递也可以做到,但有一定概率传入的参数不符合预期,且不好控制。
有一些还不错的自我修复能力是,当大模型发现参数报错的时候,会调用依赖的上一个Tools验证一下参数正确性。这种情况有一定概率修复,但并非完全可以。有时会执拗地认为自己没错:

在同一次问题中,通常链式传递表现尚可。如果是在同一会话中第二次询问,并不会获得第一次询问调用的接口的每一个信息,或者判断错误。
- 实践场景一:
Tool1:查询service的关联上下游,返回
[
{
"id": "apm@apm.service:dc7495605d8395b3788f9b54defcb826",
"type": "upstream",
"name": "gateway",
},
{
"id": "apm@apm.service:97cd7e28783143028d7e8e9c8dbce99c",
"type": "downstream",
"name": "checkout",
},
]
然后其他一些分析……省略
对话上下文中,第二次询问,请帮我查询checkout服务的信息。
模型会直接把checkout直接传入Tool2(参数只有一个id,并在Prompt注释中说明很详细)
第二次对话必须复制apm@apm.service:97cd7e28783143028d7e8e9c8dbce99c完整,才会正确传递参数。
- 实践场景二:
有一个Tool返回trace ids,返回了一个列表:
[
"93b9111f425a4b6cab6548aa68d18f04",
"e165fede431c4c178d1e7e399c92b70d",
"ce7d4e9f74634252969adfd62b509fd6",
"e824356ddf4f42d884abe7c6a2d55d66",
]
每一个值是trace id。
期望的是模型使用每一个trace id调用分析的接口,但是模型会自己传错

**经验五:在接口实现开工之前,首先以模拟数据作为尝试**
模型使用接口的方式,可能和预期不一样。接口的设计可能需要大量改动,才能让模型按照你想要的方式运作,尤其涉及Tools链式调用的时候,更要谨慎。
在设计MCP接口的时候,首先定义Tools接口信息,然后编写Tools的注释说明(引导Prompt),接着按照预期实现的返回数据,mock一些假数据直接返回。在交互的过程中不断调整接口信息、注释信息,直到假数据可以按照预期正常工作,最后再开始接口的开发,否则如果先实现接口,返工率极高,十分心痛。
**经验六:有时模型会“盲目自信”、“假装工作”。适当调低**temperature**。**
请至钉钉文档查看附件《trace 拼接错误,时间错误,自己生成.mp4》
这个case里面,trace id传错是第一个问题,之前(经验四)已经讲过。

在之后,又遇到了时间的坑。(经验二)

在发生错误之后,模型并不会提示问题,而是结合一些正确的接口输入输出数据,自己悄悄生成假模假样的数据……像不像论文里瞎编数据的无良大学生[狗头]。

这里上下游的数据也是假的,从id也能看出来。

这个问题调低tempreture可以缓解,但是并非可以完全解决,MCP本质上还是依赖生成模型,而生成模型是否适合一些严肃严格场景的接口,这里稍稍打一个问号。
除此之外,观察到如果在同一对话上下文中多次问类似实体,但id不同的实体,模型似乎并不会真正调用接口了,而是直接生成tool的输入参数,建议用户去调(谁是老板??)。
还有一种场景是受上下文影响过大,会参考上次的输出,然后新实体的关联、上下游也和上次调用的实体关联、上下游一样(糊弄我是吧??)
**总结**
MCP是标准化LLM上下文交互的开放协议,包含主机、客户端、服务器及数据源组件,支持连接本地/远程服务。用户需配置支持MCP的客户端(如Cherry Studio)和LLM API(如阿里云百炼),并可扩展官方/自定义MCP服务器。阿里云可观测2.0通过UModel统一多模态数据交互,解决系统复杂性带来的观测难题,实现从被动监控到主动预测的升级。
**MCP Server 设计注意事项和思考**
MCP作为人、LLM、业务场景融合的媒介和工具,是新一代交互工具的萌芽,但仍然存在很多设计场景上的注意点。
| 独立性 | 接口独立,一个接口做一件事,相互依赖少 | 是 | 否 |
|---|---|---|---|
| 简洁性 | 接口输入输出简洁,一目了然 | 是 | 否 |
| 宽容性 | 业务场景是否不完全依赖Tools的输出,允许模型潜在的自由输出、改写、生成,允许每次返回结果不一样(生成的内容)。 | 是 | 否 |
| 鲁棒性 | 接口传入的信息是否非常自由,不会因为非法输入导致系统失效。(参考:Google 搜索框) | 是 | 否 |
| 安全性 | 接口和业务不存在信息敏感、不存在资产交易等敏感操作 | 是 | 否 |
如果判断下来都是“是”,则非常适合MCP场景。
一些不适合的场景,通过某种转化,可以变成适合的场景。比如股票交易系统不提供交易接口,只提供股票代码查询的能力,规避安全性问题。提供股票查询后,规避返回股票信息具体细节,而引导模型输出报告,模型会输出对这支股票的看多看空思路和依据,提升宽容性。
同时也可以发现,MCP天生适合理解业务短平快的需求场景,如果想解决一个非常复杂的任务,MCP接口可能还不够,需要A2A + 更高级的协作、超长上下文的理解。
**一些优秀的MCP应用场景**
- bing 搜索、Github搜索、网盘搜索等各类搜索引擎 MCP
理由:独立、简洁(返回Top5相关)、宽容(一定程度上)、鲁棒、安全。
- 高德地图 MCP 生成旅游路线
理由:独立、宽容(每次不同甚至有新鲜感)、鲁棒(有高德地图稳定的系统为支持)、安全。
- Web浏览器自动化,相关爬取、统计、分析
理由:独立、简洁(输出约束一下)、宽容、鲁棒。
- FileSystem
理由:独立、简洁、宽容(理由是linux命令行llm非常懂,不太可能写错)、安全(不提供rm能力)
- Redis
理由:独立、简洁、宽容(非严肃Redis数据库)
**可观测2.0 + AI的未来展望**
MCP适用于短平快的独立接口能力,对于复杂需求,更适合A2A或更强大的模式。更多结合AI的能力,不仅仅有MCP,可观测2.0 + AI,还有更多可能。

**参考**
- https://modelcontextprotocol.io/introduction
- https://github.com/punkpeye/awesome-mcp-clients
- https://modelcontextprotocol.io/clients
- https://deepchat.thinkinai.xyz/
- https://help.aliyun.com/zh/model-studio/get-api-key
- https://github.com/punkpeye/awesome-mcp-servers
- https://modelcontextprotocol.io/examples
- https://github.com/modelcontextprotocol/servers
- https://ata.atatech.org/articles/11020220022
- https://ata.atatech.org/articles/11020252410
- https://ata.atatech.org/articles/11020344825