mcpField
T
testing-mcp-tools-locally

testing-mcp-tools-locally

github.com/posthog测试与质量9561 次安装今天更新
代码仓库

关于 testing-mcp-tools-locally

这是一份在 PostHog 中本地测试 managed migrations MCP 工具的指南,涵盖环境准备、带权限范围的员工凭据、工具发现及针对本地 API 的端到端调用。

主要功能

  1. 01启动本地开发环境
  2. 02准备 Postgres 和批量导入测试数据
  3. 03创建员工用户和带权限范围的 API 密钥
  4. 04通过 MCP 测试工具发现与调用
  5. 05排查身份验证和连接问题

适用场景

  1. 01检查员工密钥可见的支持工具
  2. 02验证缺少权限范围及非员工情形
  3. 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。

本应能看到工具的密钥却看不到工具

按顺序检查:

  1. 密钥明确包含 batch_import_support:read——* 不匹配隐藏作用域。
  2. 密钥也包含 user:read(或 *)——发现阶段的 staff 检查会读取 /api/users/@me/,并在检查失败时默认拒绝。
  3. 密钥所属用户的 is_staff = True。
  4. 密钥从创建时起就带有这些作用域——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 状态预置数据,以避免此情况。

各层防护的端到端流程

请求会经过两个相互独立的层:

  1. MCP 发现(呈现层):只有当密钥明确包含某个 OAuth 隐藏作用域,且 /api/users/@me/ 确认 is_staff 时,需要该作用域的工具才会显示——否则会隐藏,默认拒绝(services/mcp/src/lib/staff-only-tools.ts)。
  2. Django 强制执行(安全边界):IsAuthenticated + IsStaffUser + APIScopePermission,其中 scope_object = "INTERNAL" 且作用域为 batch_import_support:read。会话只需具备 staff 身份;PAT 需要具备 staff 身份和明确的作用域;仅有 * 的密钥始终会返回 403。

评论

支持 Markdown