基于MCP协议构建Tempo分布式追踪AI查询服务器实战

张开发
2026/5/16 3:18:31 15 分钟阅读

分享文章

基于MCP协议构建Tempo分布式追踪AI查询服务器实战
1. 项目概述一个为Tempo设计的MCP服务器最近在折腾可观测性栈特别是分布式追踪这块发现Tempo虽然作为后端存储很强大但日常查询和排查问题时总得在Grafana界面、命令行工具和文档之间来回切换效率有点打折扣。于是我花时间研究并实现了一个专门为Tempo设计的MCP服务器——ivelin-web/tempo-mcp-server。简单来说它就像一个“智能翻译官”和“能力扩展器”让你能通过像Claude Desktop、Cursor这类支持MCP协议的AI助手直接用自然语言来查询和分析你的追踪数据。这个项目本质上是一个桥梁。它一端对接Tempo的API理解如何查询Trace、Span如何搜索特定服务或操作另一端则遵循Model Context Protocol标准将这些查询能力“翻译”成AI助手能理解和调用的工具。这样一来你就不再需要记忆复杂的查询语法或频繁切换工具了。比如你可以直接对AI助手说“帮我找一下今天下午订单服务中耗时超过2秒的CreateOrder操作”或者“展示用户服务在最近一小时内错误率最高的几个Trace”。服务器会帮你构建正确的查询从Tempo拉取数据并以清晰、结构化的方式返回给AI助手呈现给你。它非常适合正在使用或考虑使用Tempo作为分布式追踪存储的开发者、SRE和运维工程师。无论你是想快速定位生产环境中的性能瓶颈还是日常开发中验证微服务间的调用链路这个工具都能显著提升你的排查效率。接下来我会详细拆解这个项目的设计思路、核心实现以及我在搭建和调试过程中积累的一些实战经验。2. 核心架构与设计思路拆解2.1 为什么选择MCP作为接入层Model Context Protocol是由Anthropic提出的一种开放协议旨在标准化AI助手与外部工具、数据源之间的通信方式。选择为Tempo构建MCP服务器而非传统的CLI或Web插件主要基于以下几点考量首先是交互范式的升级。传统的运维操作依赖于记忆命令和参数而MCP允许我们将Tempo的查询能力封装成一个个“工具”ToolsAI助手可以动态地发现、理解并调用这些工具。这意味着用户可以使用自然语言描述他们的排查意图由AI来理解意图并匹配正确的工具和参数降低了使用门槛。例如“查看服务A调用服务B的延迟”这个意图可以被解析为调用“搜索Trace”工具并自动填充服务名称、操作名称等过滤条件。其次是上下文集成能力。MCP服务器可以提供“资源”Resources比如当前部署的服务列表、常用的查询模板等。AI助手可以将这些资源作为背景知识使得对话更连贯。例如当用户连续询问“服务X的健康状况”和“它调用了哪些下游服务”时AI能记住上下文中的服务X并在后续查询中自动引用。最后是生态和未来兼容性。MCP作为一个新兴但发展迅速的协议正在被越来越多的AI原生应用支持。基于MCP构建意味着这个Tempo服务器未来可以无缝接入任何兼容MCP的客户端而不需要为每个客户端如Claude Desktop、Cursor、Windsurf单独开发适配器极大地扩展了工具的可用性。2.2 服务器端核心职责分解这个MCP服务器的核心职责非常清晰主要分为三个层次协议层实现严格遵循MCP协议规范实现SSEServer-Sent Events通信。这一层负责处理客户端的初始握手initialize、工具列表同步tools/list、工具调用tools/call等标准消息。它需要维护会话状态并按照协议要求的JSON格式序列化和反序列化消息。业务逻辑层工具封装这是服务器的“大脑”。它将Tempo的具体功能映射为MCP工具。每一个工具都有明确的输入参数符合JSON Schema定义和输出处理逻辑。例如search_traces工具对应Tempo的搜索API。它需要接收service_name,operation_name,tags,min_duration,max_duration,start_time,end_time等参数然后将其转换为Tempo的查询语言如TraceQL发起HTTP请求。get_trace_by_id工具对应通过Trace ID获取完整Trace详情的API。它处理Trace ID的输入调用Tempo的/api/traces/{traceId}端点并将返回的复杂嵌套的Span数据整理成更易读的树形或列表结构。get_services或get_operations工具对应获取已索引服务列表和操作列表的API。这类工具通常不需要复杂参数主要用于为AI助手提供上下文资源。Tempo客户端与适配层这一层封装了与Tempo集群的所有HTTP通信。它需要处理认证如果Tempo配置了认证、构建正确的请求URL、设置超时、解析响应以及处理各种HTTP错误状态如404 Trace未找到502后端错误等。一个健壮的适配层还需要考虑重试逻辑、链路追踪为查询Tempo的请求本身加上Trace ID和响应数据的缓存策略对于get_services这类不常变化的数据。2.3 技术栈选型与权衡在实现层面有几个关键的技术选型决策语言选择Node.js vs. Go vs. Python我最终选择了Node.js。原因在于MCP社区早期工具和示例多以Node.js为主生态较好例如官方modelcontextprotocol/sdk库对于需要快速迭代和处理大量异步I/O网络请求的服务器来说Node.js的事件驱动模型非常合适此外JavaScript/TypeScript在数据转换JSON处理和原型开发速度上也有优势。当然如果追求极致的性能和二进制部署便利性Go也是一个绝佳的选择但这意味着需要从更底层实现MCP协议。核心依赖库modelcontextprotocol/sdk这是构建MCP服务器的基石SDK它封装了协议细节让我们可以专注于工具和资源的实现。使用它避免了手动处理SSE和协议版本兼容性问题。axios用于向Tempo发送HTTP请求。相比原生http模块axios提供了更简洁的API、拦截器、自动JSON转换和更清晰的错误处理这对于构建稳定的客户端适配层很重要。zod或types/json-schema用于定义和验证工具的参数。MCP要求工具输入必须符合JSON Schema。使用zod可以在TypeScript运行时获得优秀的类型安全和验证能力确保传入Tempo查询的参数是有效且安全的。配置管理服务器的行为由配置驱动主要配置项包括TEMPO_BASE_URLTempo后端的地址例如http://localhost:3100。SERVER_PORTMCP服务器自身监听的端口。QUERY_TIMEOUT_MS查询Tempo的超时时间防止长时间查询阻塞。CACHE_TTL_MS对于元数据查询的缓存生存时间。 这些配置通过环境变量传入保证了部署的灵活性。3. 核心工具的实现与细节剖析3.1 搜索工具的实现与TraceQL转换search_traces是整个服务器中最核心、最复杂的工具。用户的自然语言查询最终要在这里被转换为Tempo能理解的TraceQL查询。参数设计工具定义了以下主要输入参数均标记为非必填但AI助手通常会尝试填充service_name(string): 服务名对应TraceQL中的{.service.name “…”}。operation_name(string): 操作名对应{.name “…”}。tags(object): 键值对标签用于更精细的过滤例如{.http.method”GET”}。这里设计为一个对象如{“http.method”: “GET”, “error”: “true”}。min_duration/max_duration(string): 持续时间过滤如“2s”,“100ms”。对应{.duration 2s}。start_time/end_time(string): ISO 8601时间字符串定义查询时间范围。TraceQL动态构建核心逻辑是根据提供的参数动态拼接TraceQL查询字符串。这里有几个关键点条件组合所有提供的条件默认以AND逻辑连接。例如同时提供service_name和min_duration则生成{.service.name”order-service” .duration 2s}。标签处理tags对象中的每个键值对需要转换为TraceQL的标签条件。需要特别注意值的类型字符串值需要引号数值和布尔值则不需要。例如{“error”: true}转换为{.error true}。时间范围start_time和end_time并不直接体现在TraceQL语句中而是作为查询参数传递给Tempo的搜索API/api/search或/api/v2/search。TraceQL本身也支持时间范围筛选但通过API参数控制更为通用。空值处理如果所有参数都为空则构建一个空的或默认的查询如最近1小时的所有Trace避免生成无效查询。// 简化的构建逻辑示例 function buildTraceQLQuery(params) { const conditions []; if (params.service_name) { conditions.push(.service.name ${params.service_name}); } if (params.operation_name) { conditions.push(.name ${params.operation_name}); } if (params.tags) { for (const [key, value] of Object.entries(params.tags)) { if (typeof value string) { conditions.push(.${key} ${value}); } else { conditions.push(.${key} ${value}); } } } if (params.min_duration) { conditions.push(.duration ${params.min_duration}); } if (params.max_duration) { conditions.push(.duration ${params.max_duration}); } return conditions.length 0 ? {${conditions.join( )}} : ; }API调用与结果格式化构建好TraceQL后调用Tempo的搜索端点。这里需要注意Tempo的API版本。v2版本的搜索API/api/v2/search功能更强大。请求需要包含qTraceQL、start和endUnix时间戳参数。 返回的结果是一个Trace列表每个Trace包含traceID、rootServiceName、rootTraceName、startTimeUnixNano、durationMs等字段。MCP工具需要将这些结果格式化为对AI助手友好的文本或结构化数据。通常我们会提取最关键的信息如Trace ID、根服务、根操作、持续时间和开始时间以清晰的列表形式呈现并确保Trace ID是可点击或易于复制的方便后续深入查看。注意TraceQL的性能影响复杂的TraceQL查询尤其是涉及大量OR条件或正则表达式的查询可能会对Tempo后端造成较大压力。在工具实现时可以考虑对查询复杂度进行初步评估或添加查询超时设置避免单个查询拖垮整个系统。3.2 详情查看工具与Span树形化get_trace_by_id工具接收一个trace_id参数调用Tempo的/api/traces/{traceId}端点。Tempo返回的数据通常是按照Span收集顺序的扁平数组要理解调用链路需要将其重建为树形结构。树形化算法这是该工具的核心算法。每个Span都有spanID和parentSpanID字段。根Span的parentSpanID为空。首先创建一个以spanID为键的Map便于快速查找。遍历所有Span为每个Span初始化一个children数组。再次遍历对于每个Span如果其parentSpanID不为空则找到对应的父Span并将当前Span加入到父Span的children数组中。最后找出所有parentSpanID为空的Span它们就是树的根一个Trace可能有多个根但在理想情况下只有一个。结果呈现将树形结构转换为文本表示通常使用缩进格式。除了显示Span的基本信息操作名、服务名、持续时间、状态码一个非常有用的技巧是计算并显示每个Span在其父Span时间轴上的相对开始时间。这能直观地看出并行调用和串行调用。Trace: abc123def456 ├─ [0ms] order-service: CreateOrder (200, 150ms) │ ├─ [10ms] user-service: GetUserInfo (200, 45ms) │ ├─ [60ms] inventory-service: ReserveStock (200, 70ms) │ └─ [135ms] payment-service: Charge (200, 10ms) └─ [160ms] notification-service: SendConfirmation (200, 5ms)这样的呈现方式让调用层级和时间关系一目了然。3.3 元数据工具与缓存策略get_services和get_operations这类工具查询的是Tempo的元数据API如/api/v2/search/tags,/api/v2/search/autocomplete。这些数据变化不频繁但查询可能相对频繁例如每次对话开始时AI助手为了了解环境都可能调用。缓存实现为了避免对Tempo元数据API的重复冲击引入内存缓存是必要的。可以使用类似node-cache的库。缓存键通常由工具名和可能的参数组成如services:all。缓存时效设置一个合理的TTL例如5分钟或10分钟。这个时间不能太长以免获取到过时的服务列表也不能太短失去缓存意义。缓存失效除了TTL失效也可以考虑提供一个手动清除缓存的MCP工具用于管理目的或者在检测到特定错误时主动清除缓存。错误处理与降级即使有缓存对Tempo的原始调用也可能失败。在工具实现中需要做好错误处理如果缓存中有数据且未过期即使Tempo API暂时失败也可以返回缓存数据带一个“数据可能稍旧”的提示。如果缓存为空且Tempo API失败工具应向AI助手返回清晰的错误信息说明元数据暂时不可用并建议用户稍后重试或检查Tempo集群状态。对于get_operations这类可能依赖service_name参数的工具当参数未提供时合理的降级策略是返回一个空列表或提示用户需要指定服务名而不是直接报错。4. 开发、调试与部署实战4.1 本地开发环境搭建与调试技巧项目初始化mkdir tempo-mcp-server cd tempo-mcp-server npm init -y npm install modelcontextprotocol/sdk axios zod npm install -D typescript ts-node-dev types/node初始化TypeScript配置tsconfig.json确保target为ES2020或更高module为commonjs取决于你的运行时。调试MCP服务器调试MCP服务器有其特殊性因为它是一个长期运行的SSE服务器通过stdio与客户端通信。最有效的本地调试方法是使用MCP Inspector或直接与一个测试客户端对接。使用MCP Inspector这是官方提供的调试工具。你可以通过npx modelcontextprotocol/inspector启动它然后配置它连接到你的服务器通过stdio或socket。Inspector会提供一个UI界面让你可以手动列出工具、调用工具并查看原始请求和响应这对于验证协议合规性和工具逻辑至关重要。集成测试为每个工具函数编写单元测试模拟Tempo的HTTP响应。使用jest和axios-mock-adapter是不错的选择。确保测试覆盖参数验证、TraceQL构建、错误处理和数据格式化等关键路径。日志记录在服务器代码的关键位置如收到调用请求、发送Tempo查询前、收到Tempo响应后添加详细的日志。日志应输出到stderr因为MCP协议使用stdout进行通信。结构化日志如JSON格式便于后续分析。与Claude Desktop集成测试这是最终的验收环节。在Claude Desktop的配置文件中例如~/Library/Application Support/Claude/claude_desktop_config.json添加你的服务器配置{ mcpServers: { tempo: { command: node, args: [/absolute/path/to/your/server/build/index.js], env: { TEMPO_BASE_URL: http://localhost:3100 } } } }重启Claude Desktop后你可以在对话中尝试使用工具。一个关键的调试技巧是观察Claude Desktop的开发者控制台如果提供或系统日志其中常常包含MCP通信的错误信息是定位问题的最直接来源。4.2 配置管理与生产环境考量环境变量所有配置Tempo地址、端口、超时、缓存TTL必须通过环境变量注入。可以使用dotenv在开发时从.env文件加载在生产环境中则由容器编排系统如Kubernetes ConfigMap或服务器管理平台提供。安全考虑Tempo认证如果Tempo集群启用了认证如Bearer Token、Basic Auth服务器需要安全地处理凭证。绝对不要将凭证硬编码在代码中。最佳实践是通过环境变量如TEMPO_AUTH_TOKEN传入并在发起请求时将其设置在Authorization头中。对于更复杂的场景可以考虑集成Vault等密钥管理服务。服务器暴露MCP服务器本身通常通过stdio与本地客户端通信不直接暴露网络端口这减少了攻击面。如果你将其部署为网络服务不推荐常规做法则必须考虑添加TLS加密和身份验证。查询限制为防止恶意或错误的大范围查询拖慢Tempo应在服务器层面实施限制。例如限制单次查询的最大时间范围如不能超过24小时或对TraceQL查询的复杂度进行简单的规则检查。健康检查与监控为生产部署的服务器添加健康检查端点如果以HTTP服务器模式运行或信号处理。监控服务器的内存使用警惕缓存内存泄漏、错误日志频率以及对Tempo后端API的调用延迟和错误率。这些指标能帮助你及时发现性能瓶颈或依赖服务故障。4.3 性能优化与扩展性思考连接管理与池化如果查询量很大为每个工具调用都创建新的HTTP连接去访问Tempo是低效的。使用axios实例并配置httpAgent和httpsAgent来启用连接池可以显著提升性能。异步处理与流式响应对于可能返回大量数据的搜索操作MCP协议支持服务器端流式响应。这意味着服务器可以在从Tempo获取到部分结果时就立即发送给客户端而不是等待所有数据都获取完毕。这能极大提升用户感知速度。实现流式响应需要更精细地控制MCP的消息发送时序。扩展更多工具当前工具集聚焦在Trace的查询和查看上。未来可以扩展更多运维相关的工具例如compare_traces比较两个Trace ID的差异用于分析回归。analyze_service_dependencies基于一段时间内的Trace数据生成服务依赖图。estimate_error_impact分析某个错误Trace影响了多少用户请求。 这些工具需要更复杂的数据处理逻辑可能涉及多次查询Tempo并在内存中进行聚合分析。支持多Tempo数据源一个高级功能是让服务器支持配置多个Tempo后端例如不同环境或不同区域。工具调用时可以增加一个datasource参数来选择查询哪个后端。这要求服务器的配置和客户端适配层进行相应的抽象和改造。5. 常见问题与排查指南在实际开发和使用的过程中我遇到了一些典型问题这里记录下来供大家参考。5.1 协议与连接类问题问题Claude Desktop无法连接服务器提示“Failed to initialize server”。排查步骤检查命令路径首先确认claude_desktop_config.json中的command和args路径绝对正确并且该Node.js脚本具有可执行权限。检查服务器日志确保你的服务器进程成功启动并且没有在初始化阶段崩溃。查看服务器打印到stderr的日志看是否有未捕获的异常。验证MCP握手使用MCP Inspector单独连接你的服务器看是否能完成初始握手initialize和工具列表获取。Inspector能提供最直接的协议级错误信息。检查环境变量确认通过env配置传递的环境变量被服务器正确读取。有时在桌面环境中环境变量的作用域可能和预期不同。根本原因绝大多数情况下是服务器启动失败或初始化过程中抛出异常导致stdio通道过早关闭。仔细检查服务器入口文件的语法错误和依赖缺失。问题工具调用超时或无响应。排查步骤服务器端超时设置检查服务器中axios向Tempo发起请求的超时设置。如果Tempo响应慢可能触发服务器端的超时导致工具调用失败。适当增加QUERY_TIMEOUT_MS。Tempo集群状态直接使用curl或Postman模拟工具发送的请求测试Tempo API是否正常且响应速度是否可接受。查询复杂度检查AI助手生成的查询参数是否过于宽泛例如时间范围长达一周且无其他过滤导致Tempo查询时间过长。可以在服务器日志中记录生成的TraceQL和查询耗时。客户端超时某些MCP客户端也有自己的调用超时设置。如果服务器处理时间过长客户端可能主动断开。需要优化查询或与客户端配置协调。根本原因链路中的某个环节网络、Tempo、服务器处理耗时过长超过了某一方的等待阈值。5.2 数据与查询类问题问题搜索工具返回的结果为空但确信数据存在。排查步骤验证时间范围这是最常见的原因。检查工具调用中的start_time和end_time参数是否正确。Tempo的搜索依赖于索引确保查询时间在Trace数据的时间戳范围内。检查TraceQL在服务器日志中查看最终构建的TraceQL语句。将其复制到Grafana的Tempo数据源查询中或直接使用Tempo的搜索接口验证看是否能返回预期结果。检查标签格式特别注意tags参数中值的类型。在TraceQL中.error true和.error “true”是不同的。确保服务器构建的查询与数据中实际的标签类型匹配。确认Tempo索引Tempo需要为标签建立索引才能被高效搜索。确认你查询的标签如http.method是否在Tempo的索引配置中。根本原因查询条件与数据不匹配或Tempo的索引未覆盖查询字段。问题获取Trace详情时Span树形化显示错乱或丢失部分Span。排查步骤验证原始数据首先直接调用Tempo的/api/traces/{traceId}接口查看返回的原始Span数组。检查spanID和parentSpanID字段是否完整、正确。是否存在parentSpanID指向不存在的spanID的情况数据损坏。检查树形化算法重点检查算法中处理parentSpanID为空根Span的逻辑以及将子Span插入父children数组的逻辑。一个常见的错误是在构建Map之前或之后错误地修改了原始数据。处理多根情况分布式追踪中一个Trace理论上只有一个根但由于数据收集或上报的时序问题有时可能出现多个无父Span的节点。你的树形化算法和呈现逻辑需要能妥善处理这种情况例如将它们作为平行的子树展示。根本原因追踪数据本身的问题或树形化算法的边界情况处理不完善。5.3 性能与稳定性问题问题服务器运行一段时间后内存占用持续升高。排查步骤检查缓存如果实现了内存缓存这是首要怀疑对象。确认缓存是否有正确的TTL失效机制或者缓存键的数量是否无限增长例如为每个唯一的查询都创建了缓存键。考虑使用具有LRU最近最少使用淘汰策略的缓存库。检查未完成的异步操作确保所有向Tempo发起的HTTP请求都有超时和错误处理避免请求挂起导致相关资源无法释放。使用分析工具使用Node.js的内存分析工具如node --inspect配合Chrome DevTools或heapdump生成堆内存快照分析内存中累积的对象类型。根本原因通常是资源缓存条目、Promise、回调未按预期释放导致的内存泄漏。问题高并发查询时服务器响应变慢或出错。排查步骤监控Tempo后端首先排除Tempo集群本身的压力。高并发查询可能直接压垮Tempo导致其响应变慢或返回错误进而拖慢服务器。检查Node.js事件循环使用Async Hooks或监控工具检查是否有同步的CPU密集型操作或阻塞I/O如同步文件读写阻塞了事件循环。实施限流在服务器端为工具调用添加简单的限流机制例如使用bottleneck库控制同一时间向Tempo发起的并发请求数起到保护下游的作用。优化查询鼓励用户或指导AI助手构建更精确的查询避免全表扫描式的大范围搜索。根本原因下游服务Tempo成为瓶颈或服务器自身缺乏对并发资源的有效管理。这个项目从构思到实现让我对MCP协议的实践和Tempo的查询能力有了更深的理解。最大的体会是将专业工具的能力通过标准协议暴露给AI助手真正创造了一种“对话式运维”的新体验。它并没有替代Grafana这样的专业可视化工具而是在快速、临时的交互式排查场景下提供了一个高效的补充。如果你也在构建类似的可观测性工具不妨从MCP这个协议开始尝试它的设计思想对于构建下一代AI原生应用工具链很有启发性。在实现过程中多花时间在错误处理和日志上它们会在调试时为你节省大量时间。

更多文章