内容
# @amplitude/mcp-analytics
Amplitude MCP Analytics SDK — 模型上下文协议服务器使用情况跟踪,用于 Amplitude Analytics。
> **状态:** 预览。服务器和工具检测、默认事件集、身份解析和自定义事件现已可用。传输和相关性处理(stdio 和 Streamable HTTP,跨协议修订版)已在后台为您处理。
## 安装
```bash
pnpm add @amplitude/mcp-analytics @amplitude/analytics-node @modelcontextprotocol/sdk
```
`@amplitude/analytics-node` 和 `@modelcontextprotocol/sdk` 是对等依赖 — 您的 MCP 服务器已经依赖于后者。
## 快速入门
```ts
import { createMcpAnalytics } from '@amplitude/mcp-analytics';
const analytics = createMcpAnalytics({
apiKey: process.env.AMPLITUDE_API_KEY!,
serverName: 'my-mcp-server',
serverVersion: '1.0.0',
});
// 绑定服务器(启用分析 + 发出连接事件),然后包装您的工具处理程序。顺序很重要:instrumentServer() 必须在 connect() 之前运行。
analytics.instrumentServer(server, { authType: 'oauth' });
server.tool(
'search_docs',
schema,
analytics.instrumentTool(
async (args, extra) => doSearch(args), // 您的处理程序,不变
{ name: 'search_docs' },
),
);
await server.connect(transport);
```
要重用您已经拥有的 Amplitude 客户端,请传入它而不是 `apiKey`:
```ts
createMcpAnalytics({ amplitude, serverName: '...', serverVersion: '...' });
```
## 检测您的服务器
两个步骤,都包装您已经拥有的东西 — 处理程序签名不会改变。
**`instrumentServer(server, options?)`** 绑定 SDK 到您的 MCP 服务器。它自动检测传输,捕获客户端/服务器握手,并发出默认的连接事件。在 `server.connect()` 之前调用它 — 那时传输变得可用。它是幂等的,并返回相同的服务器。
**`instrumentTool(handler, meta)`** 包装一个工具处理程序。返回的函数与您传入的函数具有相同的形状 (`(args, extra)` 带有模式,`(extra)` 不带),因此它可以直接放入 `server.tool(...)`。每次调用它都会发出 `[MCP] Tool Call Response`,包含时间、错误和大小详细信息。
```ts
analytics.instrumentServer(server);
server.tool('search', schema, analytics.instrumentTool(
async (args, extra) => doSearch(args),
{ name: 'search', owner: 'docs-team', extra: { 'feature flag': 'new-ranker' } },
));
```
> **`instrumentTool` 需要 `instrumentServer`.** 如果服务器从未绑定,则包装器是一个 **no-op passthrough**:您的处理程序运行不受影响,不会发出任何信息,并且会记录一次警告。检测工具永远不会改变其行为。
## 默认事件
一旦服务器绑定并包装了其工具,SDK 就会自动发出这些事件:
| 事件 | 何时 | 值得注意的属性 |
| -- | -- | -- |
| `[MCP] Session Initialized` | 连接握手(stdio + 旧版 Streamable HTTP) | 客户端/服务器身份、 `[MCP] Transport` 、 `[MCP] Auth Type` |
| `[MCP] Session Ended` | 传输关闭(相同的传输) | `[MCP] Session Duration` |
| `[MCP] Tools Listed` | `tools/list` 请求 | `[MCP] Tool Count` 、 `[MCP] Tool Names` (上限)、 `[MCP] Response Duration` 、 `[MCP] Response Size` |
| `[MCP] Tool Call Response` | 每次检测到的工具调用 | `[MCP] Is Error` 、 `[MCP] Error Message`/`[MCP] Error Type`/`[MCP] Error HTTP Status` 、 `[MCP] Response Duration` 、 `[MCP] Request Size` 、 `[MCP] Response Size` 、 `[MCP] Rationale` (按需,见下文) |
| `[MCP] Tool Calls Rejected` | `tools/call` 请求失败,在任何工具回调运行之前(未知/禁用工具,输入模式验证) | `[MCP] Attempted Tool Name` (未经验证的输入 — 不保留 `[MCP] Tool Name`)、 `[MCP] Error Message` 、 `[MCP] Response Duration` 、 `[MCP] Response Size` 、 `[MCP] Response HTTP Status` |
所有事件名称和属性都以 `[MCP] ` 前缀,因此它们永远不会与同一项目中其他 Amplitude SDK 的同名事件/属性冲突。
此表格是总结。完整的参考资料 — 每个属性及其存在时间、身份解析、传输细节和错误分类 — 位于 [`docs/events.md`](./docs/events.md)。
会话事件模拟实际协议会话,仅在stdio和旧版(`2025-11-25`)Streamable HTTP上存在。在无状态(`2026-07-28+`)HTTP上没有会话握手,因此 `[MCP] Session Initialized` / `[MCP] Session Ended` 不发出,而是构造。前面的每个事件还携带共享上下文属性(身份、客户端/服务器、传输、跟踪相关性)。
## 身份
`user_id` 必须与您为同一用户发送到 Amplitude 的内容匹配。SDK 永远不会从身份验证中猜测它 — 您通过合适的路径提供它:
```ts
// 1. 与服务器绑定。限定该绑定范围 — 构建每个请求一个 McpServer 的主机可以安全地传入每个请求的数值;并发绑定永远不会相互覆盖。
analytics.instrumentServer(server, {
userId: 'user-123',
tenant: { groupType: 'org id', groupValue: '456' },
});
// 2. 在请求内部,在处理程序中(覆盖其他所有内容)。
analytics.instrumentTool(async (args, extra) => {
analytics.setIdentity({ userId: myAuth.getLoginId(extra) });
return doWork(args);
}, { name: 'search' });
// 3. 按需,从请求的 authInfo 中派生(您映射声明)。
analytics.instrumentTool(handler, { name: 'search' }, {
resolveIdentity: (authInfo) => ({ userId: authInfo?.sub as string }),
});
```
解析顺序(第一个匹配项获胜):`setIdentity()` → `resolveIdentity()` → `instrumentServer` 选项 → 相关性锚点 → 匿名底线。当没有提供明确的身份但存在相关性锚点时,SDK 会发出准确的聚合数据,仅在合成的 `device_id` 下 — 从来不是污染性占位符,从来不是虚构用户。
如果也没有锚点 — 完全无状态的情况,没有身份和租户 — 每个请求都会生成一个新的随机 `device_id`,没有跨调用缝合,因此默认情况下这些事件 **被丢弃** 而不是膨胀您的用户数(参见 [`docs/events.md`](./docs/events.md))。选择发出它们作为匿名聚合数据,启用 `emitAnonymousEvent: true`:
```ts
import { MCPAnalyticsConfig } from '@amplitude/mcp-analytics';
new MCPAnalyticsConfig({ emitAnonymousEvent: true });
```
## 理由
代理客户端通常为工具调用提供自由文本理由(“为什么我要调用此工具”)。如果您的服务器接收到一个 — 作为工具参数,在 `_meta` 中,在标头中,或根据您的约定 — 将其传递给 SDK,它将作为保留的 `[MCP] Rationale` 属性发出在工具调用事件和同一调用的每个工具范围自定义事件中:
```ts
analytics.instrumentTool(async (args, extra) => {
if (typeof args.rationale === 'string') {
analytics.setRationale(args.rationale);
}
return doWork(args);
}, { name: 'search' });
```
SDK 从不从工具输入中读取理由本身:它是内容承载的自由文本,因此发出它是明确的选择,并且它位于您的约定中。在检测到的处理程序内部的任何深度调用(像 `setIdentity`);截断为 1000 个字符;最后写入赢。完全省略时不设置。
## 错误 HTTP 状态
当工具调用因带有 HTTP 状态的错误而失败时(`err.status` 或 `err.statusCode` — 常见的 Node 约定),工具调用事件包括 `[MCP] Error HTTP Status`。这是工具遇到的失败状态(上游 API 响应,HTTP 形状错误),**不是** MCP 传输状态 — 根据 MCP 规范,工具失败在带内返回,因此传输通常即使此属性为 4xx/5xx 也会回答 200。
对于 SDK 无法嗅探的错误形状,在构建错误时显式设置它:`analytics.toolError(ctx, { code, message, httpStatus: 502 })`。
相关但不同的:`[MCP] Response HTTP Status` 是 HTTP 响应本身的传输级状态。检测到的工具包装器永远不会发出它(调度工具调用会回答 200;包装器在响应写入之前发出)。默认的 `[MCP] Tool Calls Rejected` 事件在 Streamable HTTP 上携带它(协议级拒绝回答 200,错误在 JSON-RPC 正文中)。对于您自己发出的事件,在调用 `trackToolEvent` 之前,在上下文的 `request` 信息上设置 `responseHttpStatus`。
## 选择要捕获的内容
所有默认事件默认情况下都处于启用状态。使用 `autocapture` — 一个布尔值用于所有事件,或者一个对象用于独立控制系列:
```ts
import { createMcpAnalytics, MCPAnalyticsConfig } from '@amplitude/mcp-analytics';
createMcpAnalytics({
apiKey: process.env.AMPLITUDE_API_KEY!,
serverName: 'my-mcp-server',
serverVersion: '1.0.0',
config: new MCPAnalyticsConfig({
autocapture: { serverEvents: false }, // 保留工具调用事件,丢弃连接事件
}),
});
```
`autocapture: false` 禁用所有默认事件; `{ serverEvents, toolCalls }` 切换每个系列。`toolCalls` 涵盖 `[MCP] Tool Call Response` 和 `[MCP] Tool Call Rejected`。`serverEvents` 可以进一步分为 `sessionLifecycle`( `[MCP] Session Initialized` / `Ended`)和 `toolsListed`( `[MCP] Tools Listed`)— 例如,每个 HTTP 请求构建的服务器通常需要 `{ sessionLifecycle: false, toolsListed: true }`,因为它们的传输在每个请求的末尾关闭,而不是在会话结束时。自定义事件(如下文)不受影响。
## 上下文(`ctx`)
每个跟踪的事件都携带一个特定于调用的上下文对象。您可以构造一个并显式传递给跟踪 API,或者通过 `runWithContext` 暴露它,以便更深的调用堆栈可以通过 `getCurrentContext()` 读取它。
```ts
import {
createServerContext,
createToolContext,
runWithContext,
} from '@amplitude/mcp-analytics/context';
const serverCtx = createServerContext({
server: { name: 'my-mcp-server', version: '1.0.0' },
transport: 'stdio',
});
const toolCtx = createToolContext(serverCtx, { name: 'search_docs' });
runWithContext(toolCtx, () => {
// 如果需要,可以在此处使用 getCurrentContext()
});
```
类型和帮助程序也从主入口(`@amplitude/mcp-analytics`)重新导出。
您通常不会手动构建 `ctx` — `instrumentServer` / `instrumentTool` 为您构造并注入它。当在检测到的处理程序外部发出事件时,请使用这些工厂。
## 自定义事件属性
每个事件都带有一组 **保留** 属性,SDK 从上下文中派生 — 身份、会话/跟踪相关性、客户端/服务器身份和(对于工具事件)工具元数据。您可以在这些基础上附加自己的属性:
- **`extra`** — 上下文上的富集袋。在服务器范围内放置 `extra`(`instrumentServer` 选项中的 `extra`)或工具范围内的 `extra`(工具元数据中的 `extra`),它们会随从该范围派生的每个事件一起传输 — 包括默认事件。
- **`properties`** — `trackServerEvent` / `trackToolEvent` 的每个调用参数,用于特定于该事件的值。
### 优先级
当同一个键出现在多个地方时,合并顺序是固定的 — 后面的源覆盖前面的源:
```
保留(SDK 派生) < extra(上下文袋) < properties(每个调用)
```
- **`properties`** 值胜过任何具有相同键的值 — 包括保留属性(显式、每个调用的值是最有意图的)。
- **`extra`** 值覆盖保留属性但丢失给 `properties`。
- 在 **默认事件** 上,SDK 的结果值(`[MCP] Is Error` 、 `[MCP] Response Duration` ,...)作为每个调用的 `properties` 传递,因此冲突的 `extra` 键无法覆盖它们。
保留名称都带有 `[MCP] ` 前缀 — 在您自己的键中避免它,冲突永远不会出现。
### 删除 `extra` 包
默认情况下包含 `extra` 属性。要为单个事件省略它们,请传入 `{ dropExtraProps: true }`:
```ts
analytics.trackToolEvent(ctx, 'my event', { foo: 'bar' }, { dropExtraProps: true });
```
值按提供的方式发送 — SDK 不会转义或编辑它们。在数据呈现的位置应用任何输出编码。
## 架构决策
### 与 `@amplitude/ai` 分开的仓库
MCP 服务器分析是一种与代理分析不同的产品。不同的受众(MCP 服务器操作员与代理开发人员),不同的领域模型(服务器/会话/工具调用与代理/回合/消息),以及不同的发布节奏。保持仓库分离,让每个仓库在其自己的时间表上独立演化,而不耦合不相关的重大更改。
### `-node` 后缀
v1 仅限 Node/TypeScript。可能会有 Python SDK 跟进;后缀留下了空间,而不必强制未来重命名。
### 模仿 `@amplitude/ai` 以实现 DX,而不是领域模型
构建工具(tsdown、vitest、biome)、仓库布局、构造函数形状、模拟测试客户端、子路径导出和发布管道都镜像 Amplitude-AI-Node,以便贡献者可以在两个仓库之间看到熟悉的模式。领域模型 — 事件、属性、身份、上下文 — 是 MCP 原生的,旨在不重用代理词汇。
### 核心供应商,无硬依赖
一组共享的低级实用程序(交付代理 + 钩子,服务器无缝刷新会计)从 `@amplitude/ai` 供应商,而不是作为依赖项。这保持两个包在运行时独立 — 没有共享包,没有版本耦合 — 同时重用经过战斗测试的代码。贡献者注释关于供应商策略的生活在 `VENDORED.md`。
## 开发
```bash
pnpm install
pnpm build
pnpm test
pnpm lint
```
连接信息
猜你喜欢
暂无推荐服务器