From 7282eb46eba5245d1bb8f5325e92181164faaaa0 Mon Sep 17 00:00:00 2001 From: yanivt Date: Mon, 27 Jul 2026 19:21:05 +0300 Subject: [PATCH 1/3] AX-1775 - Add jfrog mcp --- .codex-plugin/plugin.json | 7 ++++--- .mcp.json | 5 +++++ README.md | 22 ++++++++++++++++++---- package.json | 4 ++-- scripts/validate.mjs | 36 ++++++++++++++++++++++++++++++++++-- scripts/validate.test.mjs | 17 +++++++++++++++-- 6 files changed, 78 insertions(+), 13 deletions(-) create mode 100644 .mcp.json diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 3687af7..818c3fb 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,12 +1,13 @@ { "name": "jfrog", - "version": "0.1.0", - "description": "JFrog skills for Codex — interact with the JFrog Platform.", + "version": "0.1.1", + "description": "JFrog skills and the JFrog MCP server for Codex — interact with the JFrog Platform.", "author": { "name": "JFrog", "email": "michaelto@jfrog.com" }, "license": "Apache-2.0", "repository": "https://github.com/jfrog/codex-plugin", - "keywords": ["jfrog", "artifactory", "codex", "plugin", "skills"], + "keywords": ["jfrog", "artifactory", "codex", "plugin", "skills", "mcp"], "skills": "./skills/", + "mcpServers": "./.mcp.json", "interface": { "displayName": "JFrog", "shortDescription": "JFrog Platform skills for Codex", diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..9529c7e --- /dev/null +++ b/.mcp.json @@ -0,0 +1,5 @@ +{ + "jfrog": { + "url": "https://.jfrog.io/mcp" + } +} diff --git a/README.md b/README.md index d6b3734..382e50b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # JFrog Plugin for OpenAI Codex -Delivers the JFrog skills to OpenAI Codex. **Phase 1: skills only** (no MCP yet). +Delivers the JFrog skills **and the JFrog MCP server** to OpenAI Codex. ## Install (git marketplace) @@ -10,11 +10,25 @@ Delivers the JFrog skills to OpenAI Codex. **Phase 1: skills only** (no MCP yet) /reload-plugins ``` +## MCP server + +The plugin bundles the `jfrog` MCP server ([`.mcp.json`](.mcp.json)). After +installing, do two things: + +1. **Set your host.** + (`~/.codex/plugins/.../codex-plugin/jfrog//.mcp.json`) and replace + `` with your JFrog subdomain - for `https://mycompany.jfrog.io` + use `mycompany` (self-hosted: replace the whole host). +2. **Log in (OAuth).** Run `codex mcp login jfrog` and finish the browser + sign-in. + +Restart Codex; `/mcp` now lists `jfrog` with its tools. + ## Skills -- `jfrog` — interact with the JFrog Platform (CLI, MCP, REST/GraphQL). -- `jfrog-ai-catalog-skills` — discover, install, manage, and publish agent skills from the JFrog AI Catalog via `jf skills` and Agent Guard. -- `jfrog-package-safety-and-download` — package safety checks and Artifactory-routed downloads. +- `jfrog` - interact with the JFrog Platform (CLI, MCP, REST/GraphQL). +- `jfrog-ai-catalog-skills` - discover, install, manage, and publish agent skills from the JFrog AI Catalog via `jf skills` and Agent Guard. +- `jfrog-package-safety-and-download` - package safety checks and Artifactory-routed downloads. Skills are vendored from [`jfrog/jfrog-skills`](https://github.com/jfrog/jfrog-skills), pinned in `scripts/sync-skills-vendor.json`. Bump the pin and run `npm run sync-skills` to update. See [`VENDOR.md`](VENDOR.md) for the full picture. diff --git a/package.json b/package.json index 2258ef8..059d4d5 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "codex-plugin", - "version": "0.1.0", + "version": "0.1.1", "private": true, "type": "module", - "description": "JFrog skills plugin for OpenAI Codex (Phase 1: skills only).", + "description": "JFrog skills and MCP server plugin for OpenAI Codex.", "license": "Apache-2.0", "scripts": { "sync-skills": "node scripts/sync-skills.mjs", diff --git a/scripts/validate.mjs b/scripts/validate.mjs index dd9c5fc..d66079a 100644 --- a/scripts/validate.mjs +++ b/scripts/validate.mjs @@ -51,7 +51,30 @@ export function validateManifest(obj) { if (obj?.skills != null && obj.skills !== './skills/') { errors.push('plugin.json: "skills" must be "./skills/"'); } - if (obj?.mcpServers != null) errors.push('plugin.json: "mcpServers" must not be set in Phase 1'); + if (obj?.mcpServers != null && obj.mcpServers !== './.mcp.json') { + errors.push('plugin.json: "mcpServers" must be "./.mcp.json"'); + } + return errors; +} + +// Validates the bundled MCP config referenced by plugin.json's "mcpServers". +// Accepts a direct server map ({ "": {...} }) or a wrapped object +// ({ "mcp_servers": {...} }) — both forms Codex supports. Each server must +// declare a "url" (streamable HTTP) or a "command" (stdio). +export function validateMcp(obj) { + if (obj == null || typeof obj !== 'object' || Array.isArray(obj)) { + return ['.mcp.json: must be a JSON object']; + } + const map = obj.mcp_servers && typeof obj.mcp_servers === 'object' ? obj.mcp_servers : obj; + const names = Object.keys(map); + if (names.length === 0) return ['.mcp.json: no MCP servers defined']; + const errors = []; + for (const n of names) { + const s = map[n]; + if (s == null || typeof s !== 'object' || (s.url == null && s.command == null)) { + errors.push(`.mcp.json: server "${n}" must set "url" or "command"`); + } + } return errors; } @@ -82,7 +105,16 @@ function main() { if (dirs.length === 0) errors.push('skills/: no skill directories found'); for (const d of dirs) errors.push(...validateSkillDir(skillsRoot, d)); - errors.push(...validateManifest(JSON.parse(readFileSync(join(root, '.codex-plugin/plugin.json'), 'utf8')))); + const manifest = JSON.parse(readFileSync(join(root, '.codex-plugin/plugin.json'), 'utf8')); + errors.push(...validateManifest(manifest)); + if (manifest?.mcpServers) { + const mcpPath = join(root, '.mcp.json'); + if (!existsSync(mcpPath)) { + errors.push('.mcp.json: referenced by plugin.json but not found'); + } else { + errors.push(...validateMcp(JSON.parse(readFileSync(mcpPath, 'utf8')))); + } + } errors.push(...validateMarketplace(JSON.parse(readFileSync(join(root, '.agents/plugins/marketplace.json'), 'utf8')))); if (errors.length) { diff --git a/scripts/validate.test.mjs b/scripts/validate.test.mjs index 598a03d..4f92010 100644 --- a/scripts/validate.test.mjs +++ b/scripts/validate.test.mjs @@ -11,6 +11,7 @@ import { validateSkillDir, validateManifest, validateMarketplace, + validateMcp, } from './validate.mjs'; function writeSkill(root, dir, body) { @@ -58,18 +59,30 @@ test('validateSkillDir flags an empty block-scalar description', () => { assert.ok(errors.some((e) => e.includes('description'))); }); -test('validateManifest requires fields, the skills pointer, and forbids mcpServers (Phase 1)', () => { +test('validateManifest requires fields, the skills pointer, and accepts the mcpServers pointer', () => { assert.deepEqual( validateManifest({ name: 'jfrog', version: '0.1.0', description: 'd', skills: './skills/' }), [] ); assert.ok(validateManifest({ name: 'jfrog' }).some((e) => e.includes('version'))); + assert.deepEqual( + validateManifest({ name: 'jfrog', version: '0.1.0', description: 'd', skills: './skills/', mcpServers: './.mcp.json' }), + [] + ); assert.ok( - validateManifest({ name: 'jfrog', version: '0.1.0', description: 'd', skills: './skills/', mcpServers: './.mcp.json' }) + validateManifest({ name: 'jfrog', version: '0.1.0', description: 'd', skills: './skills/', mcpServers: './mcp.json' }) .some((e) => e.includes('mcpServers')) ); }); +test('validateMcp accepts direct and wrapped server maps, flags servers without url/command', () => { + assert.deepEqual(validateMcp({ jfrog: { url: 'https://x/mcp' } }), []); + assert.deepEqual(validateMcp({ mcp_servers: { jfrog: { url: 'https://x/mcp' } } }), []); + assert.deepEqual(validateMcp({ local: { command: 'node', args: ['s.js'] } }), []); + assert.ok(validateMcp({}).some((e) => e.includes('no MCP servers'))); + assert.ok(validateMcp({ jfrog: {} }).some((e) => e.includes('url') && e.includes('command'))); +}); + test('validateMarketplace requires a local source with a ./ path', () => { assert.deepEqual( validateMarketplace({ name: 'm', plugins: [{ name: 'jfrog', source: { source: 'local', path: './' } }] }), From eb3164e144c899b67d4e310ad474ccf5ecfabdf7 Mon Sep 17 00:00:00 2001 From: yanivt Date: Mon, 27 Jul 2026 19:25:08 +0300 Subject: [PATCH 2/3] AX-1775 - Change README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 382e50b..a5ef7f4 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,9 @@ Delivers the JFrog skills **and the JFrog MCP server** to OpenAI Codex. The plugin bundles the `jfrog` MCP server ([`.mcp.json`](.mcp.json)). After installing, do two things: -1. **Set your host.** - (`~/.codex/plugins/.../codex-plugin/jfrog//.mcp.json`) and replace - `` with your JFrog subdomain - for `https://mycompany.jfrog.io` +1. **Set your host.** Find the install path with `codex plugin list` (the + `jfrog@codex-plugin` row) and edit `/.mcp.json`. Replace `` + in the `url` with your JFrog subdomain - for `https://mycompany.jfrog.io` use `mycompany` (self-hosted: replace the whole host). 2. **Log in (OAuth).** Run `codex mcp login jfrog` and finish the browser sign-in. From 965bee08cde59a9c55cd30e74bcc5216a14d155d Mon Sep 17 00:00:00 2001 From: yanivt Date: Wed, 29 Jul 2026 13:10:20 +0300 Subject: [PATCH 3/3] AX-1775 - Change to JFROG_PLATFORM_URL --- .mcp.json | 2 +- README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.mcp.json b/.mcp.json index 9529c7e..6ca4272 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,5 +1,5 @@ { "jfrog": { - "url": "https://.jfrog.io/mcp" + "url": "https:///mcp" } } diff --git a/README.md b/README.md index a5ef7f4..3da59c0 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ The plugin bundles the `jfrog` MCP server ([`.mcp.json`](.mcp.json)). After installing, do two things: 1. **Set your host.** Find the install path with `codex plugin list` (the - `jfrog@codex-plugin` row) and edit `/.mcp.json`. Replace `` - in the `url` with your JFrog subdomain - for `https://mycompany.jfrog.io` - use `mycompany` (self-hosted: replace the whole host). + `jfrog@codex-plugin` row) and edit `/.mcp.json`. Replace + `` in the `url` with your full JFrog Platform host - e.g. + `mycompany.jfrog.io` (or your self-hosted / custom domain). 2. **Log in (OAuth).** Run `codex mcp login jfrog` and finish the browser sign-in.