Expose only public MCP support in marketplace

Constraint: The public marketplace must not contain internal MCP endpoints, shared keys, project keys, or user-token workflows.

Rejected: Publishing every configured KeyInfo MCP server | internal knowledge, Firecrawl, and Outline servers are not public distribution targets.

Confidence: high

Scope-risk: moderate

Directive: Keep MCP publication behind an explicit allowlist and verify hidden plugin metadata before pushing.

Tested: python scripts/validate_marketplace.py; temporary CODEX_HOME marketplace list/add/remove for mcp-playwright; hidden rg scan for internal MCP URLs and key markers.

Not-tested: Live Playwright MCP browser tool invocation inside a fresh Codex session.
This commit is contained in:
KeyInfo Bot
2026-06-12 17:42:38 +08:00
parent 9d6c8cded3
commit fca5c80516
9 changed files with 237 additions and 105 deletions
+12
View File
@@ -255,6 +255,18 @@
"authentication": "ON_INSTALL"
},
"category": "文档处理"
},
{
"name": "mcp-playwright",
"source": {
"source": "local",
"path": "./plugins/codex/plugins/mcp-playwright"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "MCP"
}
]
}
+67 -83
View File
@@ -1,36 +1,31 @@
# EAPIL Codex Plugin Marketplace
# EAPIL Codex 插件市场
This repository is a Codex plugin marketplace. Its primary purpose is to give AI agents a stable way to install EAPIL-maintained public skills and third-party Codex plugins.
这是 EAPIL 维护的 Codex 插件市场。首要用途是让 AI agent 能稳定安装公共 skills、MCP 插件和第三方 Codex plugin
## For AI Agents
## AI 的安装流程
When a user asks to install or use an EAPIL skill/plugin, do this first:
当用户要求安装或使用 EAPIL 插件时,优先执行:
```bash
codex plugin marketplace add https://git.playones.com/arechen/EapilSkillMarket.git --ref main
codex plugin marketplace upgrade eapil-skill-market
codex plugin list --marketplace eapil-skill-market
codex plugin add <plugin-name>@eapil-skill-market
codex plugin add <plugin-id>@eapil-skill-market
```
Use the exact plugin id from the plugin table below. Do not guess translated names.
使用 `codex plugin list --marketplace eapil-skill-market` 输出里的精确 `plugin-id`。不要猜测中文名、翻译名或 display name
After installation:
如果插件刚安装到当前 Codex 会话中,启动新的 Codex thread 或新的 Codex 调用后再依赖新增 skills/hooks/MCP/app 配置。
卸载插件:
```bash
codex plugin list --marketplace eapil-skill-market
codex plugin remove <plugin-id>@eapil-skill-market
```
If the plugin was just installed in the current Codex session, start a new Codex thread or new invocation before relying on newly installed skills being loaded into context.
## 本地调试安装
To remove a plugin:
```bash
codex plugin remove <plugin-name>@eapil-skill-market
```
## Local Debug Install
From this repository checkout:
从本仓库 checkout 调试:
```bash
codex plugin marketplace add .
@@ -38,116 +33,105 @@ codex plugin list --marketplace eapil-skill-market
codex plugin add docx@eapil-skill-market
```
For a temporary clean test:
临时干净环境验收:
```bash
tmp_home="$(mktemp -d)"
CODEX_HOME="$tmp_home" codex plugin marketplace add /path/to/EapilSkillMarket
CODEX_HOME="$tmp_home" codex plugin add <plugin-name>@eapil-skill-market
CODEX_HOME="$tmp_home" codex plugin remove <plugin-name>@eapil-skill-market
CODEX_HOME="$tmp_home" codex plugin list --marketplace eapil-skill-market
CODEX_HOME="$tmp_home" codex plugin add <plugin-id>@eapil-skill-market
CODEX_HOME="$tmp_home" codex plugin remove <plugin-id>@eapil-skill-market
CODEX_HOME="$tmp_home" codex plugin marketplace remove eapil-skill-market
rm -rf "$tmp_home"
```
## Sparse Checkout
## MCP 插件
Normal installation should leave sparse paths empty. If a constrained environment requires sparse checkout, include both marketplace metadata and plugin contents:
当前公开仓库只发布 `mcp-playwright`。KeyInfo 主项目里还维护了内部知识库、Firecrawl、Outline 等 MCP,但这些依赖内部地址、项目 Key、shared key 或用户私有 token,不能进入公开 Git 仓库。
```bash
codex plugin marketplace add https://git.playones.com/arechen/EapilSkillMarket.git --ref main --sparse .agents --sparse plugins/codex
`mcp-playwright` 是本地 MCP 插件:
- 插件 ID`mcp-playwright`
- MCP server id`playwright`
- 本地命令:`bunx @playwright/mcp@latest`
- 鉴权:无
安装后如需覆盖启用状态或审批策略,可在用户 Codex 配置里追加:
```toml
[plugins."mcp-playwright@eapil-skill-market".mcp_servers.playwright]
enabled = true
```
## Current Plugins
后端自动同步公开 MCP 时只读取 `SKILL_MARKET_PUBLIC_MCP_IDS` allowlist,默认值是 `playwright`。除非确认目标 MCP 可公开、无内部 URL/密钥泄露风险,否则不要加入 allowlist。
| Plugin id | Display name | Category | Use when |
| --- | --- | --- | --- |
| `docx` | docx | 文档处理 | Create, edit, review, or extract Word documents. |
| `eapil-gpt2-image` | eapil-gpt2-image | 设计 | Generate or edit images through EAPIL's OpenAI-compatible image API. |
| `find-skills` | find-skills | 工具 | Help users discover suitable installable skills. |
| `frontend-slides` | Frontend Slides | 文档处理 | Create animated fixed-stage HTML slides or convert PPT/PPTX to HTML slides. |
| `guizang-ppt-skill` | Guizang PPT Skill | 文档处理 | Create magazine-style or Swiss-style horizontal web PPTs. |
| `html-ppt` | HTML PPT Studio | 文档处理 | Create professional static HTML presentations with themes, layouts, and animations. |
| `karpathy-guidelines` | karpathy-guidelines | 开发工具 | Apply Andrej Karpathy-inspired AI coding and model-training guidance. |
| `literature-review` | literature-review | 文档处理 | Assist with literature review and academic paper drafting workflows. |
| `oh-my-codex` | oh-my-codex | 开发工具 | Add OMX workflows, plugin-scoped hooks, MCP config, and multi-agent orchestration for Codex CLI. |
| `paper2code` | paper2code | 文档处理 | Turn arXiv papers into concise referenced Python implementations. |
| `pdf` | pdf | 文档处理 | Extract, create, merge, split, fill, or analyze PDF files. |
| `postgres` | postgres | 数据分析 | Run read-only PostgreSQL schema inspection and SELECT analysis. |
| `ppt-master` | PPT Master | 文档处理 | Convert source documents into high-quality SVG pages and export PPTX decks. |
| `ppt-translator` | ppt-translator | 文档处理 | Translate PPT/PPTX files while preserving layout through a render-and-verify loop. |
| `pptx` | pptx | 文档处理 | Create, edit, analyze, or annotate PowerPoint presentations. |
| `shadcn` | shadcn/ui | 开发工具 | Work with shadcn/ui projects, components, registries, and presets. |
| `superpowers` | Superpowers | 开发工具 | Use structured planning, TDD, debugging, code review, and delivery workflows. |
| `threejs` | threejs | 设计 | Build Three.js scenes, materials, lighting, animation, postprocessing, and loaders. |
| `ui-ux-pro-max` | UI/UX Pro Max | 设计 | Use a UI/UX design knowledge base for style, typography, accessibility, charts, and frontend stack choices. |
| `xlsx` | xlsx | 文档处理 | Create, edit, analyze, recalculate, or visualize spreadsheets. |
| `patent-application-drafting-skill` | 专利申请撰写 | 文档处理 | Draft Chinese patent technical disclosures, claims, specifications, abstracts, and related Word documents. |
## 官方插件能力
## Plugin Capabilities
本市场按 Codex 官方插件能力校验和发布,插件可以包含:
Most plugins expose one or more skills under `skills/`. Codex also supports richer plugin components, and this market may publish them when upstream plugins use them:
- `skills``./skills/` 下的一个或多个 `SKILL.md`
- `mcpServers`:通常是 `./.mcp.json`,允许 MCP-only 插件。
- `apps`:通常是 `./.app.json`,用于 app connector metadata。
- `hooks`:插件作用域 Codex hooks,可为路径、路径数组、inline object 或 object 数组。
- `interface` / `assets``interface.logo``interface.composerIcon``interface.screenshots` 可引用插件内 `assets/` 文件或公开 URL。
- `hooks`: plugin-scoped Codex hooks, usually declared as `./hooks/hooks.json`.
- `mcpServers`: MCP server metadata, usually declared as `./.mcp.json`.
- `apps`: app connector metadata, usually declared as `./.app.json`.
市场校验器会检查 plugin id、manifest `name` 和目录名必须是 ASCII kebab-case;中文只能用于 `interface.displayName`、说明文案和分类等展示字段。
`oh-my-codex` currently uses all three. It requires the user's environment to have `oh-my-codex` / `omx` installed and set up for full runtime behavior. Codex may ask the user to trust plugin hooks before running them.
## Repository Layout
## 仓库结构
```text
.agents/plugins/marketplace.json
plugins/codex/
.agents/plugins/marketplace.json
plugins/<plugin-name>/
plugins/<plugin-id>/
.codex-plugin/plugin.json
skills/<skill-name>/SKILL.md
hooks/ # optional
.mcp.json # optional
.app.json # optional
skills/<skill-name>/SKILL.md # 可选
.mcp.json # 可选
.app.json # 可选
hooks/ # 可选
assets/ # 可选
config/external-sources.json
scripts/validate_marketplace.py
```
## Maintenance Rules
## 维护规则
- This is a distribution repository. Keep the published Git history shallow and small.
- Plugin ids, directory names, and skill `name` values use English kebab-case.
- Preserve upstream display names such as `shadcn/ui`, `oh-my-codex`, and `Guizang PPT Skill`; do not translate proper names.
- User-facing Chinese copy belongs in `description`, `shortDescription`, `longDescription`, `category`, and `defaultPrompt`.
- Update this README whenever a public skill/plugin is added, replaced, renamed, removed, or gains a new major capability such as hooks, MCP, or app metadata.
- Do not sync README-only preview GIFs/screenshots into this repository. Skill/reference images needed by the skill should be compressed to WebP instead.
- Third-party plugin localization belongs in `config/external-sources.json` through `manifestOverrides` and `skillDescriptions`; do not hand-edit generated plugin manifests.
- README 正文统一使用简体中文;插件 ID、命令、配置键和上游专有名称保留原文。
- 日常发布保留普通线性 Git 历史,使用普通 `git add -A``git commit``git push`,让 Codex marketplace 刷新获得增量传输收益。
- 只有误同步大文件或用户明确要求历史清理时,才做一次性历史压缩。
- 每次新增、替换、重命名、移除 public skill/plugin,或新增 hooks/MCP/apps/assets 等能力后,都要同步更新本 README 和 `plugins/codex/README.md`
- 不要同步 README-only 预览 GIF/截图。skill/reference 真正需要的图片保留并压缩为 WebP。
- 第三方插件本地化写入 `config/external-sources.json``manifestOverrides``skillDescriptions`,不要直接手改生成 manifest。
- KeyInfo 内部 MCP 默认不公开。公开 MCP 必须经过 `SKILL_MARKET_PUBLIC_MCP_IDS` allowlist,并确认无内部 URL、shared key、项目 key 或用户 token 泄露风险。
## Third-Party Sync
## 第三方同步
Third-party sources are tracked in `config/external-sources.json`.
第三方来源记录在 `config/external-sources.json`
Supported adapters:
支持的适配器:
- `codex-plugin`: upstream is already a Codex plugin.
- `claude-skill`: upstream is a Claude-style skill that is converted into a Codex plugin.
- `codex-plugin`:上游已经是 Codex plugin
- `claude-skill`:上游是 Claude-style skill,会转换为 Codex plugin
Independent PowerShell sync:
PowerShell 独立同步:
```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\sync_external_plugins.ps1
```
With proxy:
带代理:
```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\sync_external_plugins.ps1 -Proxy http://127.0.0.1:2085
```
## Validation
Before publishing changes:
## 发布前验证
```bash
python scripts/validate_marketplace.py
python /Users/eapil/.codex/skills/.system/plugin-creator/scripts/validate_plugin.py plugins/codex/plugins/<plugin-name>
rg -n "<plugin-name>|<display-name>" README.md plugins/codex/README.md
rg -n "<plugin-id>|<display-name>" README.md plugins/codex/README.md
```
For new plugins, also install from a temporary `CODEX_HOME` and run the smallest safe invocation that proves Codex can load the skill/plugin.
对包含 `skills` 的插件,可额外运行系统 `validate_plugin.py`。不要用旧 validator 降级官方能力;MCP-only、app-only 或 hooks-only 插件以本仓库 marketplace 校验和 Codex 实装验收为准。
对新增插件,还要使用临时 `CODEX_HOME` 安装,并执行最小安全调用,证明 Codex 能加载该插件。MCP-only 插件至少要验证 marketplace list/add 成功,并检查 `.mcp.json` 已进入插件缓存;真实 MCP tool 调用可能依赖本地命令、浏览器权限或 token。
@@ -255,6 +255,18 @@
"authentication": "ON_INSTALL"
},
"category": "文档处理"
},
{
"name": "mcp-playwright",
"source": {
"source": "local",
"path": "./plugins/mcp-playwright"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "MCP"
}
]
}
+9 -6
View File
@@ -1,24 +1,27 @@
# Codex Marketplace Sparse Entry
# Codex 插件入口
This directory is the sparse checkout entry for the EAPIL Codex Plugin Marketplace. AI agents should normally install the marketplace from the repository root URL:
这个目录是 EAPIL Codex 插件市场的 sparse checkout 入口。AI agent 通常应从仓库根 URL 安装市场:
```bash
codex plugin marketplace add https://git.playones.com/arechen/EapilSkillMarket.git --ref main
codex plugin marketplace upgrade eapil-skill-market
codex plugin list --marketplace eapil-skill-market
codex plugin add <plugin-name>@eapil-skill-market
codex plugin add <plugin-id>@eapil-skill-market
```
Use sparse checkout only when the environment needs a smaller checkout:
只有在受限环境需要更小 checkout 时才使用 sparse 参数:
```bash
codex plugin marketplace add https://git.playones.com/arechen/EapilSkillMarket.git --ref main --sparse .agents --sparse plugins/codex
```
Local debug from the repository root:
从仓库根目录本地调试:
```bash
codex plugin marketplace add .
codex plugin add docx@eapil-skill-market
```
Each `plugins/<plugin-name>` directory is an installable Codex plugin. Plugins can include plain skills plus official Codex plugin components such as `hooks`, `.mcp.json`, and `.app.json`. See the root `README.md` for the full plugin table and maintenance rules.
每个 `plugins/<plugin-id>` 目录都是一个可安装 Codex plugin。插件可以包含官方支持的 `skills``mcpServers``apps``hooks``interface``assets`。当前公开 MCP 只包含 `mcp-playwright`KeyInfo 内部 MCP 不发布到公开仓库。
完整插件清单、MCP 说明和维护规则见根目录 `README.md`
@@ -0,0 +1,38 @@
{
"name": "mcp-playwright",
"version": "0.1.0",
"description": "Playwright 是由 Microsoft 开发并维护的开源浏览器自动化框架,专为现代 Web 应用设计。它提供了一套统一的 API,支持 Chromium、Firefox 和 WebKit 三大主流浏览器引擎,适用于端到端测试、网页数据采集及自动化操作等场景。",
"author": {
"name": "EAPIL",
"url": "https://git.playones.com/arechen/EapilSkillMarket"
},
"homepage": "https://git.playones.com/arechen/EapilSkillMarket",
"repository": "https://git.playones.com/arechen/EapilSkillMarket",
"license": "Proprietary",
"keywords": [
"eapil",
"codex-mcp",
"mcp",
"playwright"
],
"mcpServers": "./.mcp.json",
"interface": {
"displayName": "playwright浏览器自动化操作",
"shortDescription": "Playwright 是由 Microsoft 开发并维护的开源浏览器自动化框架,专为现代 Web 应用设计。它提供了一套统一的 API,支持 Chromium、Firefox 和 WebKit 三大主流浏览器引擎,适用于端到端测试、网页数据采集及自动化操作等场景。",
"longDescription": "Playwright 是由 Microsoft 开发并维护的开源浏览器自动化框架,专为现代 Web 应用设计。它提供了一套统一的 API,支持 Chromium、Firefox 和 WebKit 三大主流浏览器引擎,适用于端到端测试、网页数据采集及自动化操作等场景。",
"developerName": "EAPIL",
"category": "MCP",
"capabilities": [
"Interactive",
"Read"
],
"defaultPrompt": [
"使用 playwright浏览器自动化操作 MCP 查询或处理这个任务。"
],
"websiteURL": "https://git.playones.com/arechen/EapilSkillMarket",
"privacyPolicyURL": "https://git.playones.com/arechen/EapilSkillMarket",
"termsOfServiceURL": "https://git.playones.com/arechen/EapilSkillMarket",
"brandColor": "#0F766E",
"screenshots": []
}
}
@@ -0,0 +1,19 @@
{
"mcpServers": {
"playwright": {
"command": "bunx",
"args": [
"@playwright/mcp@latest"
],
"default_tools_approval_mode": "approve",
"tools": {
"browser_navigate": {
"approval_mode": "approve"
},
"browser_tabs": {
"approval_mode": "approve"
}
}
}
}
}
@@ -0,0 +1,7 @@
{
"id": "playwright",
"name": "playwright浏览器自动化操作",
"version": "20260605",
"keySource": "none",
"syncedAt": "2026-06-12T09:39:35Z"
}
@@ -0,0 +1,19 @@
# playwright浏览器自动化操作
Codex MCP 插件 ID`mcp-playwright`
Playwright 是由 Microsoft 开发并维护的开源浏览器自动化框架,专为现代 Web 应用设计。它提供了一套统一的 API,支持 Chromium、Firefox 和 WebKit 三大主流浏览器引擎,适用于端到端测试、网页数据采集及自动化操作等场景。
## 运行前提
- 本地命令:`bunx @playwright/mcp@latest`
- 安装插件不会预先安装本地命令依赖;请先确认命令在当前 Codex 环境可执行。
## Codex 配置
插件自带 `.mcp.json` 声明传输配置。安装后如需调整启用状态或工具审批策略,可在用户 Codex 配置里覆盖:
```toml
[plugins."mcp-playwright@eapil-skill-market".mcp_servers.playwright]
enabled = true
```
+54 -16
View File
@@ -82,6 +82,39 @@ def validate_hooks_field(plugin_dir: Path, manifest_path: Path, hooks: object) -
raise AssertionError(f"{manifest_path} hooks must be a path, path list, inline object, or inline object list")
def validate_skills_component(plugin_dir: Path, manifest_path: Path, skills_rel: object) -> None:
require(isinstance(skills_rel, str) and skills_rel.startswith("./"), f"{manifest_path} skills path must be relative ./...")
skills_dir = validate_manifest_path(plugin_dir, manifest_path, skills_rel, "skills")
require(skills_dir.exists(), f"{manifest_path} skills directory does not exist: {skills_dir}")
skill_dirs = [path for path in skills_dir.iterdir() if path.is_dir()]
require(bool(skill_dirs), f"{manifest_path} skills component must contain at least one skill directory")
for skill_dir in skill_dirs:
require((skill_dir / "SKILL.md").exists(), f"Missing SKILL.md in {skill_dir}")
def validate_json_component(
plugin_dir: Path,
manifest_path: Path,
value: object,
field: str,
required_top_level: str,
) -> None:
require(isinstance(value, str), f"{manifest_path} {field} must be a path string")
target = validate_manifest_path(plugin_dir, manifest_path, value, field)
payload = load_json(target)
require(isinstance(payload, dict), f"{target} must be a JSON object")
require(isinstance(payload.get(required_top_level), dict), f"{target} missing object {required_top_level}")
def validate_interface_asset(plugin_dir: Path, manifest_path: Path, value: object, field: str) -> None:
if value is None or value == "":
return
require(isinstance(value, str), f"{manifest_path} interface.{field} must be a string")
if value.startswith(("http://", "https://")):
return
validate_manifest_path(plugin_dir, manifest_path, value, f"interface.{field}")
def validate_manifest(plugin_dir: Path, expected_name: str) -> None:
manifest_path = plugin_dir / ".codex-plugin" / "plugin.json"
require(manifest_path.exists(), f"Missing manifest: {manifest_path}")
@@ -91,26 +124,24 @@ def validate_manifest(plugin_dir: Path, expected_name: str) -> None:
require(PLUGIN_NAME_RE.match(expected_name) is not None, f"{manifest_path} plugin name must be ASCII kebab-case")
require(SEMVER_RE.match(str(manifest.get("version", ""))) is not None, f"{manifest_path} version must be semver-like")
require(bool(manifest.get("description")), f"{manifest_path} missing description")
if "hooks" in manifest:
validate_hooks_field(plugin_dir, manifest_path, manifest["hooks"])
author = manifest.get("author")
require(isinstance(author, dict) and bool(author.get("name")), f"{manifest_path} missing author.name")
skills_rel = manifest.get("skills")
require(isinstance(skills_rel, str) and skills_rel.startswith("./"), f"{manifest_path} skills path must be relative ./...")
skills_dir = validate_manifest_path(plugin_dir, manifest_path, skills_rel, "skills")
require(skills_dir.exists(), f"{manifest_path} skills directory does not exist: {skills_dir}")
skill_dirs = [path for path in skills_dir.iterdir() if path.is_dir()]
require(bool(skill_dirs), f"{manifest_path} must contain at least one skill directory")
for skill_dir in skill_dirs:
require((skill_dir / "SKILL.md").exists(), f"Missing SKILL.md in {skill_dir}")
declared_components = [
key
for key in ["skills", "mcpServers", "apps", "hooks"]
if key in manifest
]
require(bool(declared_components), f"{manifest_path} must declare at least one component: skills, mcpServers, apps, or hooks")
for optional_path_field in ["mcpServers", "apps"]:
value = manifest.get(optional_path_field)
if value is not None:
require(isinstance(value, str), f"{manifest_path} {optional_path_field} must be a path string")
validate_manifest_path(plugin_dir, manifest_path, value, optional_path_field)
if "skills" in manifest:
validate_skills_component(plugin_dir, manifest_path, manifest.get("skills"))
if "mcpServers" in manifest:
validate_json_component(plugin_dir, manifest_path, manifest.get("mcpServers"), "mcpServers", "mcpServers")
if "apps" in manifest:
validate_json_component(plugin_dir, manifest_path, manifest.get("apps"), "apps", "apps")
if "hooks" in manifest:
validate_hooks_field(plugin_dir, manifest_path, manifest["hooks"])
interface = manifest.get("interface")
require(isinstance(interface, dict), f"{manifest_path} missing interface object")
@@ -118,6 +149,13 @@ def validate_manifest(plugin_dir: Path, expected_name: str) -> None:
require(bool(interface.get(key)), f"{manifest_path} missing interface.{key}")
capabilities = interface.get("capabilities")
require(isinstance(capabilities, list), f"{manifest_path} interface.capabilities must be a list")
validate_interface_asset(plugin_dir, manifest_path, interface.get("logo"), "logo")
validate_interface_asset(plugin_dir, manifest_path, interface.get("composerIcon"), "composerIcon")
screenshots = interface.get("screenshots")
if screenshots is not None:
require(isinstance(screenshots, list), f"{manifest_path} interface.screenshots must be a list")
for index, screenshot in enumerate(screenshots):
validate_interface_asset(plugin_dir, manifest_path, screenshot, f"screenshots[{index}]")
def resolve_marketplace_plugin_path(marketplace_path: Path, source_path: str) -> Path: