chore(codex): 添加项目级 MCP 配置模板
This commit is contained in:
committed by
fit2cloud-chenyw
parent
f433d49ec5
commit
2f6d7c6e62
15
.codex/README.md
Normal file
15
.codex/README.md
Normal 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
21
.codex/config.toml
Normal 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
|
||||
3
.codex/dbhub_mcp/.env.example
Normal file
3
.codex/dbhub_mcp/.env.example
Normal 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"
|
||||
13
.codex/dbhub_mcp/dbhub.toml
Normal file
13
.codex/dbhub_mcp/dbhub.toml
Normal 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"
|
||||
30
.codex/dbhub_mcp/launch.py
Normal file
30
.codex/dbhub_mcp/launch.py
Normal 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)
|
||||
5
.codex/playwright_mcp/.env.example
Normal file
5
.codex/playwright_mcp/.env.example
Normal 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=""
|
||||
30
.codex/playwright_mcp/launch.py
Normal file
30
.codex/playwright_mcp/launch.py
Normal 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())
|
||||
5
.codex/tapd_mcp/.env.example
Normal file
5
.codex/tapd_mcp/.env.example
Normal 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
32
.codex/tapd_mcp/launch.py
Normal 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
3
.gitignore
vendored
@@ -57,3 +57,6 @@ core/core-frontend/src/assets/fsSvg.html
|
||||
.workbuddy/
|
||||
docs/
|
||||
|
||||
.codex/**/.env.local
|
||||
.codex/**/browser-profile/
|
||||
.codex/**/output/
|
||||
|
||||
Reference in New Issue
Block a user