Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ account with trade intelligence enabled.
├── .claude-plugin/marketplace.json # Claude Code and Copilot marketplace
├── .cursor-plugin/marketplace.json # Cursor marketplace
├── openai/review.json # OpenAI directory review cases, merged into the ZIP
├── openai/listing.json # OpenAI package name, version, and category for the existing plugin
├── openai/listing.json # OpenAI package name, version, category, and MCP URL for the existing plugin
├── scripts/
│ ├── build-openai-zip.mjs # Builds the OpenAI directory ZIP
│ └── validate.mjs
Expand Down
3 changes: 2 additions & 1 deletion openai/listing.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "app-69f5795de4a48191a35dc5c448520676",
"version": "1.1.0",
"category": "Business & Operations"
"category": "Business & Operations",
"mcpUrl": "https://mcp.terminal49.com/"
}
8 changes: 7 additions & 1 deletion scripts/build-openai-zip.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ await writeStaged(
`${JSON.stringify({ ...manifest, name: listing.name, version: listing.version, interface: { ...manifest.interface, category: listing.category }, extensions: { "com.openai": { review, publication } } }, null, 2)}\n`,
);
// The Codex-format .mcp.json in the submission guide names only the url.
const servers = Object.fromEntries(Object.entries(mcp.mcpServers).map(([name, { url }]) => [name, { url }]));
// OpenAI requires the ZIP's MCP URL to match the existing app's URL exactly.
const servers = Object.fromEntries(
Object.entries(mcp.mcpServers).map(([name, { url }]) => [name, { url: listing.mcpUrl ?? url }]),
);
await writeStaged(".mcp.json", `${JSON.stringify({ mcpServers: servers }, null, 2)}\n`);
await cp(join(pluginDir, "skills"), join(stage, "skills"), { recursive: true });
for (const icon of icons) await cp(join(pluginDir, icon), join(stage, icon));
Expand Down Expand Up @@ -69,6 +72,9 @@ for (const file of zipped) {
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(listing.name ?? "") || listing.name.length > 64) {
errors.push("openai/listing.json name must be the OpenAI package name");
}
if (listing.mcpUrl !== undefined && !/^https:\/\/mcp\.terminal49\.com(\/[a-z]*)?$/.test(listing.mcpUrl)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject path suffixes in the OpenAI MCP URL

When mcpUrl is changed to a lowercase path such as https://mcp.terminal49.com/mcp, this regex accepts it and the builder copies it directly into the ZIP, even though such paths are prohibited and would no longer exactly match the existing app URL. Validate against the one registered URL (including its required trailing slash) rather than permitting arbitrary suffixes.

AGENTS.md reference: AGENTS.md:L10-L12

Useful? React with 👍 / 👎.

errors.push("openai/listing.json mcpUrl must be a Terminal49 MCP https URL");
}
if (!listing.category) errors.push("openai/listing.json category must match the dashboard category title");
if (!/^\d+\.\d+\.\d+$/.test(listing.version ?? "")) errors.push("openai/listing.json version must be semver");
if (Object.keys(servers).length !== 1) errors.push("the ZIP must declare exactly one MCP server");
Expand Down
Loading