关于 testing-mcp-tools-locally
这是一份在 PostHog 中本地测试 managed migrations MCP 工具的指南,涵盖环境准备、带权限范围的员工凭据、工具发现及针对本地 API 的端到端调用。
主要功能
- 01启动本地开发环境
- 02准备 Postgres 和批量导入测试数据
- 03创建员工用户和带权限范围的 API 密钥
- 04通过 MCP 测试工具发现与调用
- 05排查身份验证和连接问题
适用场景
- 01检查员工密钥可见的支持工具
- 02验证缺少权限范围及非员工情形
- 03针对本地 API 运行批量导入工具
MCP 相关
指南将 Hono MCP 服务连接到本地 API,并使用 MCP Inspector 测试。内容涵盖 exec 模式下的工具发现与调用,以及员工身份和 API 权限范围检查。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ posthog/ ai-plugin --skill testing-mcp-tools-locally安装后在 Claude Code 中输入/testing-mcp-tools-locally确认已加载。
附带文件references/seed-data.md
详细介绍
在本地测试托管迁移 MCP 工具
前提条件
开发环境必须正在运行,且 Docker 服务状态正常。 批量导入支持 API 和 MCP 工具要求:
- 员工用户(
is_staff = True) - 明确包含 两种权限
batch_import_support:read和user:read的 Personal API Key - 已应用 Postgres 迁移(不需要 ClickHouse)
为什么需要这两种权限:后端仅接受 batch_import_support:read,
但 MCP 工具发现会通过 /api/users/@me/ 验证员工身份;如果密钥无法调用该接口,就会隐藏这些工具(默认拒绝)。
* 通配符不能代替其中任意一种权限——发现检查要求明确包含隐藏权限,而后端对 INTERNAL 权限的处理会直接拒绝通配符密钥。
生产环境的设置流程请参阅 docs/support-mcp-tools.md。
1. 启动开发环境
hogli start -d
hogli wait
如果 hogli wait 在 migrate-persons-db 或 migrate-behavioral-cohorts 处失败,
这些是可选的独立数据库,可以忽略。
如果在 migrate-postgres 处失败,请检查 Docker 端口转发(见下方故障排查)。
2. 运行 Postgres 迁移
hogli migrations:run
ClickHouse 迁移失败没有关系——批量导入只需要 Postgres。
3. 在 Django shell 中验证数据库连接
hogli dev:shell-plus -y -- -c "
from posthog.models import Team, User
print(Team.objects.first(), User.objects.first())
"
如果此处在端口 5432 上出现 connection refused 错误,请参阅下方故障排查。
4. 填充批量导入测试数据
使用 hogli dev:shell-plus 创建处于各种状态的 BatchImport 记录。
secrets 字段是 EncryptedJSONStringField——空 {} 会序列化为 null,
并违反 NOT NULL 约束;请始终传入非空字典。
from products.managed_migrations.backend.models.batch_imports import BatchImport
BatchImport.objects.create(
team=team,
created_by_id=user.id,
status=BatchImport.Status.PAUSED,
import_config={
'source': {'type': 's3', 'bucket': 'test', 'region': 'us-east-1', 'prefix': 'data/'},
'data_format': {'type': 'json_lines', 'skip_blanks': True, 'content': {'type': 'mixpanel'}},
'sink': {'type': 'capture'},
},
secrets={'access_key': 'test', 'secret_key': 'test'},
state={'parts': [
{'key': 'part-1', 'current_offset': 50000, 'total_size': 50000},
{'key': 'part-2', 'current_offset': 10000, 'total_size': 50000},
{'key': 'part-3'},
]},
)
完整的填充脚本涵盖所有状态,请参阅 references/seed-data.md。
重要: 本地的 batch-import-worker 进程会处理 RUNNING 记录,
并可能修改其状态(例如,因配置验证错误而暂停记录)。
若要让记录在测试期间保持稳定,请停止该 worker,或使用 COMPLETED/FAILED/PAUSED 状态。
5. 将你的用户设为员工并创建测试密钥
请创建新的密钥,而不是修改现有密钥的权限—— MCP 服务会按令牌缓存密钥的权限,因此修改权限后可能仍会返回过时的结果。
from posthog.models import User
from posthog.models.personal_api_key import PersonalAPIKey
from posthog.models.utils import generate_random_token_personal, hash_key_value
me = User.objects.first()
me.is_staff = True; me.save()
def mint(user, scopes):
token = generate_random_token_personal()
PersonalAPIKey.objects.create(user=user, label=str(scopes)[:40], secure_value=hash_key_value(token), scopes=scopes)
return token
print(mint(me, ["batch_import_support:read", "user:read"]))
要测试发现检查的负面情形,还要创建:
一个 ["*"] 密钥(工具不得出现),
一个不含 user:read 的 ["batch_import_support:read"] 密钥(工具不得出现——员工身份查询会默认拒绝),
以及为非员工用户创建的完整权限组合(工具不得出现)。
6. 直接测试 API
# List all batch imports
curl -H "Authorization: Bearer <token>" \
http://localhost:8010/api/managed_migrations_support/ | jq
# Get detail for a specific import
curl -H "Authorization: Bearer <token>" \
http://localhost:8010/api/managed_migrations_support/<uuid>/ | jq
7. 通过 MCP 测试
运行 Hono 服务,而不是 pnpm run dev。
wrangler worker(pnpm run dev,端口 8787)会将 /mcp 代理到生产环境 mcp.us.posthog.com,除非设置了 MCP_HONO_URL,
因此本地密钥会得到 401 Invalid API key。
Hono 服务会直接通过本地 API 提供 MCP 服务:
cd services/mcp
cp .dev.vars.example .dev.vars # POSTHOG_API_BASE_URL=http://localhost:8010
pnpm run dev:hono # serves http://localhost:3001/mcp
使用 PAT 作为 Bearer 请求头进行身份验证,切勿使用 OAuth 流程。 OAuth 中在结构上缺少隐藏的作用域——通过 Inspector 的 OAuth 登录进行登录,永远无法看到这些工具。
Hono 服务运行在 exec 模式:tools/list 会返回单个 exec 工具,
实际工具通过它发现并调用。
使用 MCP Inspector CLI 进行测试:
# Discovery — should list both support tools for the staff key, none for the others
npx @modelcontextprotocol/inspector --cli http://localhost:3001/mcp \
--header "Authorization: Bearer <token>" \
--method tools/call --tool-name exec --tool-arg "command=search managed-migrations-support"
# Invocation — end-to-end through Django
npx @modelcontextprotocol/inspector --cli http://localhost:3001/mcp \
--header "Authorization: Bearer <token>" \
--method tools/call --tool-name exec --tool-arg "command=call managed-migrations-support-list {}"
预期的发现矩阵:
| key | 可见工具 |
|---|---|
staff 用户,batch_import_support:read + user:read | 两者 |
staff 用户,仅有 * | 无 |
staff 用户,有 batch_import_support:read,但没有 user:read | 无(staff 查询失败时默认拒绝) |
| 非 staff 用户,两个作用域都具备 | 无(直接 API 调用也会返回 403) |
交互式 Inspector UI(http://localhost:6274)也可使用——
在连接设置中将 PAT 粘贴为 Bearer 令牌,而不要使用其 OAuth 登录。
故障排除
来自 localhost:8787 的 401 "Invalid API key"
你连接的是 wrangler worker,它会将 /mcp 代理到生产环境,因此你的本地密钥在此处无效。
请使用端口 3001 上的 Hono 服务(参见步骤 7),或在 .dev.vars 中设置 MCP_HONO_URL=http://localhost:3001。
本应能看到工具的密钥却看不到工具
按顺序检查:
- 密钥明确包含
batch_import_support:read——*不匹配隐藏作用域。 - 密钥也包含
user:read(或*)——发现阶段的 staff 检查会读取/api/users/@me/,并在检查失败时默认拒绝。 - 密钥所属用户的
is_staff = True。 - 密钥从创建时起就带有这些作用域——MCP 服务会按令牌缓存作用域,因此请创建新密钥,而不是编辑现有密钥。
主机无法访问端口 5432
posthog-db-1 Docker 容器可能有过期的端口映射
(该容器创建于数日前,当时尚未配置当前端口绑定)。
通过强制重新创建来修复:
docker compose -f docker-compose.dev.yml -f docker-compose.profiles.yml \
up -d --force-recreate db
验证:nc -z 127.0.0.1 5432 应当成功。
secrets={} 导致违反 NOT NULL 约束
EncryptedJSONStringField 会加密该值——空字典序列化后会变成 null。
请始终传入非空字典:secrets={'placeholder': 'true'}。
批量导入 worker 修改了预置记录
本地 batch-import-worker 进程会自动认领 RUNNING 记录。
如果它遇到配置验证错误(例如缺少 skip_blanks),
就会暂停导入,并在 status_message 中附上详细的 Rust 回溯。
请停止该 worker,或使用非 RUNNING 状态预置数据,以避免此情况。
各层防护的端到端流程
请求会经过两个相互独立的层:
- MCP 发现(呈现层):只有当密钥明确包含某个 OAuth 隐藏作用域,且
/api/users/@me/确认is_staff时,需要该作用域的工具才会显示——否则会隐藏,默认拒绝(services/mcp/src/lib/staff-only-tools.ts)。 - Django 强制执行(安全边界):
IsAuthenticated+IsStaffUser+APIScopePermission,其中scope_object = "INTERNAL"且作用域为batch_import_support:read。会话只需具备 staff 身份;PAT 需要具备 staff 身份和明确的作用域;仅有*的密钥始终会返回 403。