chore(codex): 添加项目级 MCP 配置模板

This commit is contained in:
fit2cloud-chenyw
2026-09-22 20:45:08 +08:00
committed by fit2cloud-chenyw
parent f433d49ec5
commit 2f6d7c6e62
10 changed files with 157 additions and 0 deletions

15
.codex/README.md Normal file
View File

@@ -0,0 +1,15 @@
# DataEase 项目 MCP
受信任项目会从 `.codex/config.toml` 加载 MCP 配置。同名项目配置覆盖个人配置,其他个人 MCP 保留。修改后重启 Codex 任务。
每位开发者按需复制各 MCP 目录的 `.env.example` 为同目录 `.env.local`,填写自己的 TAPD 项目 ID、用户名、个人令牌,以及本机 MCP 入口。值使用 JSON 字符串语法(双引号,特殊字符需要转义)。`NODE_BIN` 可填写 Node 可执行文件绝对路径。Playwright 的测试 URL 和登录账号密码仅保存在本地;启动器不会自动登录,浏览器操作需在具体任务中明确指定。
浏览器资料和截图分别保存在 `.codex/playwright_mcp/browser-profile/` 与 `output/`。本地凭据、浏览器资料和输出目录均被 Git 忽略。建议用 `chmod 600` 限制凭据文件权限。
TAPD 仅开放配置中的读取工具。查询“我的待办”时,默认查询整个项目的需求 `owner` 和缺陷 `current_owner`,不限定迭代。成员接口若返回 403,需相应接口权限。
## DBHub
`.codex/dbhub_mcp/` 提供数据库 MCP 配置模板和只读启动器。开发者可按 [DBHub 官方说明](https://github.com/bytebase/dbhub)安装 `@bytebase/dbhub@1.3.1` 到个人的 `~/.codex/mcp/dbhub/`,并在 `.env.local` 中填写实际安装入口和自己的只读数据库 DSN。由于示例中没有可安全使用的数据库连接信息,`config.toml` 默认设为 `enabled = false`;填写 DSN 后再改为 `true` 并重启任务。示例 DSN 仅作格式示意,不是项目真实连接。
启动器通过环境变量传 DSN,不在命令参数中暴露密码。DBHub 1.3.1 的只读限制在 `dbhub.toml` 中对 `execute_sql` 设置;数据库账号本身仍应授予只读权限,因为应用层 SQL 检查不能替代数据库权限。不要用生产或共享环境做配置验证。

21
.codex/config.toml Normal file
View File

@@ -0,0 +1,21 @@
[mcp_servers.tapd]
command = "python3"
args = [".codex/tapd_mcp/launch.py"]
enabled = true
startup_timeout_sec = 20
tool_timeout_sec = 60
enabled_tools = ["tapd_get_current_user", "tapd_parse_url", "tapd_get_story", "tapd_list_stories", "tapd_get_task", "tapd_list_tasks", "tapd_get_bug", "tapd_list_bugs", "tapd_get_iteration", "tapd_list_iterations", "tapd_list_comments", "tapd_get_story_related_bugs", "tapd_get_story_changes", "tapd_list_workspace_users"]
[mcp_servers.playwright]
command = "python3"
args = [".codex/playwright_mcp/launch.py"]
enabled = true
startup_timeout_sec = 30
tool_timeout_sec = 120
[mcp_servers.dbhub]
command = "python3"
args = [".codex/dbhub_mcp/launch.py"]
enabled = false
startup_timeout_sec = 30
tool_timeout_sec = 120

View File

@@ -0,0 +1,3 @@
DBHUB_DSN="mysql://readonly_user:password@127.0.0.1:3306/dataease"
DBHUB_MCP_ENTRY="/absolute/path/to/@bytebase/dbhub/dist/index.js"
NODE_BIN="node"

View File

@@ -0,0 +1,13 @@
[[sources]]
id = "dataease"
dsn = "${DSN}"
[[tools]]
name = "execute_sql"
source = "dataease"
readonly = true
max_rows = 1000
[[tools]]
name = "search_objects"
source = "dataease"

View File

@@ -0,0 +1,30 @@
"""Launch a locally installed DBHub in read-only mode."""
import json
import os
import shutil
import sys
from pathlib import Path
base = Path(__file__).resolve().parent
try:
local = {}
for raw in (base / '.env.local').read_text().splitlines():
line = raw.strip()
if not line or line.startswith('#'):
continue
key, sep, value = line.partition('=')
if not sep or key.strip() not in {'DBHUB_DSN', 'DBHUB_MCP_ENTRY', 'NODE_BIN'}:
raise ValueError()
local[key.strip()] = json.loads(value.strip())
if not all(isinstance(local.get(k), str) and local[k] for k in ('DBHUB_DSN', 'DBHUB_MCP_ENTRY')):
raise ValueError()
entry = Path(local['DBHUB_MCP_ENTRY']).expanduser()
node = local.get('NODE_BIN') or shutil.which('node')
if not entry.is_file() or not node:
raise ValueError()
except (OSError, ValueError, KeyError, TypeError):
sys.exit('DBHub MCP 配置无效;请检查 .codex/dbhub_mcp/.env.local')
env = os.environ.copy()
env['DSN'] = local['DBHUB_DSN']
os.execvpe(node, [node, str(entry), '--config', str(base / 'dbhub.toml')], env)

View File

@@ -0,0 +1,5 @@
PLAYWRIGHT_MCP_ENTRY="/absolute/path/to/@playwright/mcp/cli.js"
NODE_BIN="node"
TEST_BASE_URL="https://qa-de3.fit2cloud.cn"
TEST_USERNAME=""
TEST_PASSWORD=""

View File

@@ -0,0 +1,30 @@
"""Launch Playwright MCP with project-local browser storage."""
import json
import os
import shutil
import sys
from pathlib import Path
base = Path(__file__).resolve().parent
try:
local = {}
for raw in (base / '.env.local').read_text().splitlines():
line = raw.strip()
if not line or line.startswith('#'):
continue
key, sep, value = line.partition('=')
if not sep or key.strip() not in {'PLAYWRIGHT_MCP_ENTRY', 'TEST_BASE_URL', 'TEST_USERNAME', 'TEST_PASSWORD', 'NODE_BIN'}:
raise ValueError()
local[key.strip()] = json.loads(value.strip())
entry = Path(local['PLAYWRIGHT_MCP_ENTRY']).expanduser()
node = local.get('NODE_BIN') or shutil.which('node')
if not entry.is_file() or not node:
raise ValueError()
except (OSError, ValueError, KeyError, TypeError):
sys.exit('Playwright MCP 配置无效;请检查 .codex/playwright_mcp/.env.local')
profile = base / 'browser-profile'
output = base / 'output'
profile.mkdir(exist_ok=True, mode=0o700)
output.mkdir(exist_ok=True, mode=0o700)
os.execvpe(node, [node, str(entry), '--browser', 'chrome', '--user-data-dir', str(profile), '--output-dir', str(output)], os.environ.copy())

View File

@@ -0,0 +1,5 @@
TAPD_WORKSPACE_ID="40899682"
TAPD_CURRENT_USER="your TAPD username"
TAPD_API_TOKEN="your personal token"
TAPD_MCP_ENTRY="/absolute/path/to/tapd-mcp/dist/index.js"
NODE_BIN="node"

32
.codex/tapd_mcp/launch.py Normal file
View File

@@ -0,0 +1,32 @@
"""Launch TAPD MCP with project-local credentials."""
import json
import os
import shutil
import sys
from pathlib import Path
base = Path(__file__).resolve().parent
try:
local = {}
for raw in (base / '.env.local').read_text().splitlines():
line = raw.strip()
if not line or line.startswith('#'):
continue
key, sep, value = line.partition('=')
if not sep or key.strip() not in {'TAPD_WORKSPACE_ID', 'TAPD_CURRENT_USER', 'TAPD_API_TOKEN', 'TAPD_MCP_ENTRY', 'NODE_BIN'}:
raise ValueError()
local[key.strip()] = json.loads(value.strip())
if not all(isinstance(local.get(k), str) and local[k] for k in ('TAPD_WORKSPACE_ID', 'TAPD_CURRENT_USER', 'TAPD_API_TOKEN', 'TAPD_MCP_ENTRY')):
raise ValueError()
entry = Path(local['TAPD_MCP_ENTRY']).expanduser()
node = local.get('NODE_BIN') or shutil.which('node')
if not entry.is_file() or not node:
raise ValueError()
except (OSError, ValueError, KeyError, TypeError):
sys.exit('TAPD MCP 配置无效;请检查 .codex/tapd_mcp/.env.local')
env = os.environ.copy()
env.pop('TAPD_API_USER', None)
env.pop('TAPD_API_PASSWORD', None)
env.update({key: local[key] for key in ('TAPD_WORKSPACE_ID', 'TAPD_CURRENT_USER', 'TAPD_API_TOKEN')})
os.execvpe(node, [node, str(entry)], env)

3
.gitignore vendored
View File

@@ -57,3 +57,6 @@ core/core-frontend/src/assets/fsSvg.html
.workbuddy/
docs/
.codex/**/.env.local
.codex/**/browser-profile/
.codex/**/output/