diff --git a/marketplace.json b/marketplace.json index b0f2d1b..57a3a7f 100644 --- a/marketplace.json +++ b/marketplace.json @@ -9,7 +9,7 @@ { "name": "jfrog", "description": "JFrog Platform integration with MCP, security skills, and supply-chain best practices", - "version": "1.0.14", + "version": "1.0.15", "source": "plugin", "categories": [ "security", @@ -21,8 +21,15 @@ "agent-guard", "ai-catalog" ], - "platforms": ["darwin", "linux", "windows"], - "requiredPermissions": ["network", "shell"] + "platforms": [ + "darwin", + "linux", + "windows" + ], + "requiredPermissions": [ + "network", + "shell" + ] } ] } diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index 656fdbd..d6fbfaf 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,7 +1,10 @@ { "name": "jfrog", "description": "JFrog Platform integration with MCP, security skills, and supply-chain best practices", - "version": "1.0.14", - "author": { "name": "JFrog", "url": "https://jfrog.com" }, + "version": "1.0.15", + "author": { + "name": "JFrog", + "url": "https://jfrog.com" + }, "hooks": "hooks/hooks.json" } diff --git a/plugin/skills/jfrog-mcp-management/SKILL.md b/plugin/skills/jfrog-mcp-management/SKILL.md index ddfef9b..8f38454 100644 --- a/plugin/skills/jfrog-mcp-management/SKILL.md +++ b/plugin/skills/jfrog-mcp-management/SKILL.md @@ -1,14 +1,14 @@ --- name: jfrog-mcp-management description: >- - Use when you need to install, list, and/or remove MCP servers and tools through the JFrog Agent - Guard (npx @jfrog/agent-guard), and browse the JFrog MCP catalog. Use - whenever the user wants to add/enable/list/remove/uninstall an MCP server or - tool — even without saying "MCP" — and never install one any other way. + Use to install, list, or remove MCP servers through the JFrog Agent Guard (npx + @jfrog/agent-guard), and browse the JFrog MCP catalog. Use whenever the user + wants to add/enable/list/remove/uninstall an MCP server or tool — even without + saying "MCP" — and never install one any other way. compatibility: >- Requires network access to the npm registry and the JFrog platform, and ~/.jfrog/ write access for OAuth login and cache cleanup. -allowed-tools: Bash(claude mcp list) Bash(cursor agent mcp list) Read Edit Write +allowed-tools: Bash(claude mcp list) Bash(cursor agent mcp list) Bash(codex mcp list) Bash(opencode mcp list) Read Edit Write metadata: role: workflow --- @@ -25,12 +25,14 @@ resolved automatically by the agent. If the agent does not resolve it, determine the path by locating this SKILL.md file and using its parent directory. **Harness config (resolve once, up front).** Where MCP config lives — the file -path, the top-level JSON key, the env/secret reference syntax, and how a server -is enabled/verified/listed — varies per agent (Claude Code, Cursor, VS Code, …). -Those values are NOT hard-coded in this skill. Read +path, the top-level key, the config format (JSON or TOML), the env/secret +reference syntax, and how a server is enabled/verified/listed — varies per agent +(Claude Code, Codex, Cursor, OpenCode, VS Code, …). Those values are NOT +hard-coded in this skill. Read [references/harness-common.md](references/harness-common.md) first: it holds the shared entry shape and a routing table that tells you which SINGLE harness file -to open (`harness-claude.md`, `harness-cursor.md`, `harness-vscode.md`, …). +to open (`harness-claude.md`, `harness-codex.md`, `harness-cursor.md`, +`harness-opencode.md`, `harness-vscode.md`, …). **Read common + exactly one harness file — do NOT open the others.** Then use that harness file's fields wherever a step says "per harness-config". Adding a new agent is a new `harness-.md` file, with no change to this workflow. @@ -129,14 +131,18 @@ existing Agent Guard MCP entry or jf config; omit `--server` only on the [references/harness-common.md](references/harness-common.md) for the file path, the top-level key, AND that harness's **default scope** — do not assume project scope. Most harnesses default to the project-level file (Claude Code - `.mcp.json`, Cursor `.cursor/mcp.json`), but **VS Code defaults to the - user-level `mcp.json`** and treats `.vscode/mcp.json` as the opt-in scope. - Follow the "Config files" row in the harness file, not a fixed default here. + `.mcp.json`, Cursor `.cursor/mcp.json`), but **VS Code, Codex, and OpenCode + default to the user-level file** (VS Code `mcp.json`, Codex + `~/.codex/config.toml`, OpenCode `~/.config/opencode/opencode.json`) and treat + their project file (`.vscode/mcp.json`, trusted `.codex/config.toml`, project + `opencode.json`) as the opt-in scope. Follow the "Config files" row in the + harness file, not a fixed default here. Create the target file if missing, using that harness's top-level key (e.g. `{ "mcpServers": {} }`, or `{ "servers": {} }` for VS Code). - Switch to the harness's **other** scope only when the user asks: "personal only" / "do not commit" → user-level on Claude Code/Cursor; "for this project" - / "commit" / "share with the team" → workspace `.vscode/mcp.json` on VS Code. + / "commit" / "share with the team" → workspace `.vscode/mcp.json` on VS Code + (project `opencode.json` on OpenCode, trusted `.codex/config.toml` on Codex). Respect any per-file note in the reference (e.g. Claude Code user scope is `~/.claude.json`, NOT `projects..mcpServers`). - Do not ask which scope unless the user brings it up. @@ -190,8 +196,8 @@ Split Step 2 inputs by `isRequired`: Handling: **secrets** (`isSecret=true`) MUST be a value reference, NEVER a raw value — never take a secret in chat, echo it, or write it into config. **Non-secrets** may be a literal or a reference. For the exact syntax and, on -shell-based harnesses (Claude Code, Cursor), how the user exports/persists the -variable, see the harness file and +shell-based harnesses (Claude Code, Cursor, Codex, Devin Desktop, OpenCode), how the user +exports/persists the variable, see the harness file and [references/persisting-env-vars.md](references/persisting-env-vars.md). (VS Code prompts for `inputs` values on first start — no shell export.) @@ -291,8 +297,10 @@ add-on where the agent provides it. user scope, under that harness's top-level key) — use the file-read tool or a single `jq` invocation, NOT chained `python3 -c "..."` pipes. For each entry whose `command` is `npx` and whose `args` include `@jfrog/agent-guard`, show: - display name (JSON key), package (`mcp=` in `_JF_ARGS`), server ID (value - after `--server`), scope (project / user). + display name (the entry key; but where the harness uses a slug key — e.g. + Codex — use the package from `mcp=` instead, per that harness's List + installed), package (`mcp=` in `_JF_ARGS`), server ID (value after + `--server`), scope (project / user). 2. **If the harness exposes an MCP status command or view** (the harness-config "List installed" column — e.g. Claude Code's `claude mcp list`, Cursor/VS Code's MCP UI), use it to add live connection status per server. If none diff --git a/plugin/skills/jfrog-mcp-management/references/harness-codex.md b/plugin/skills/jfrog-mcp-management/references/harness-codex.md new file mode 100644 index 0000000..4538171 --- /dev/null +++ b/plugin/skills/jfrog-mcp-management/references/harness-codex.md @@ -0,0 +1,225 @@ +# Harness: OpenAI Codex + +Codex-specific config for the `jfrog-mcp-management` skill. Read this together +with [harness-common.md](harness-common.md) (shared entry shape and success +criterion). You reached this file because the harness is Codex (`CODEX_SANDBOX` +/ `CODEX_THREAD_ID` / `CODEX_CI`). This targets the Codex CLI / IDE extension, +which all share the same `config.toml`. + +> **Codex differs from the JSON harnesses:** the config is **TOML** - one +> `[mcp_servers.]` table per server, with the server **`` matching +> `^[a-zA-Z0-9_-]+$`** (derive a slug from `spec.packageName`, see Top-level +> key). Transport is implicit - a `command` key means stdio (omit `type`). The +> default scope is **user-level** (project scope loads only from a *trusted* +> directory). Secrets and env references use an **`env_vars` allow-list** that +> forwards named variables from the launching shell. Write the entry using the +> TOML template in **Full entry shape** below. + +## Config files + +- **Default scope: user-level.** `~/.codex/config.toml` (or + `$CODEX_HOME/config.toml` if `CODEX_HOME` is set; on Windows `~` is + `%USERPROFILE%`, i.e. `%USERPROFILE%\.codex\config.toml`) - personal, not committed, + applies to every project. Create if missing. Servers live under the + `[mcp_servers.]` table (top-level key `mcp_servers`). +- **Project:** `.codex/config.toml` in the project root - shareable via git, but + Codex loads it ONLY when the project is **trusted** (accepted the trust prompt, + or `projects."".trust_level = "trusted"` in `~/.codex/config.toml`). + Use ONLY if the user says "for this project" / "commit" / "share with the + team", and tell them it takes effect only once the directory is trusted. +- **Write to exactly one scope, never both.** User config wins where the two + overlap. Do not ask which scope unless the user brings it up. + +## Top-level key + +Use `mcp_servers` - one TOML table per server: `[mcp_servers.]`. + +**The `` MUST match `^[a-zA-Z0-9_-]+$`.** Codex rejects any other +name at startup ("Invalid MCP server name"), so when `spec.packageName` contains +characters like `.` `/` `@`, derive a **slug** for the table key: lowercase +`spec.packageName`, replace each run of characters outside `[a-z0-9_-]` with a +single `-`, and trim leading/trailing `-`. Examples: + +1. `org.example/tool` → `org-example-tool` +2. `@scope/pkg` → `scope-pkg` + +**Before writing, check for an existing `[mcp_servers.]` table with that +key.** Re-declaring a TOML key silently overwrites the earlier table (or errors on +strict parsers), and an unrelated server (another Agent Guard package, or a plain +MCP entry with no `_JF_ARGS` at all) may already own that key. Treat the key as +**yours only if its `_JF_ARGS` has `mcp=` matching exactly** - +then you are updating that entry. Otherwise, the key is occupied: append a numeric +suffix (`-2`, then `-3`, …) and keep probing until you find a free key (or one +that is already your exact package). + +The slug is only a local label - **the authoritative package identity stays in +`_JF_ARGS` (`mcp=`)**, which is what the List and Remove flows +match on. Keep `mcp=` set to the exact catalog `spec.packageName`, never the slug. + +## Value reference (env / secrets) + +In Codex, values come from two `env` mechanisms: + +- **`env` table** - inline literal values only. Use it for the non-secret + `_JF_ARGS` string, and for any non-secret you choose to write literally. +- **`env_vars` array** - an allow-list of variable NAMES that Codex forwards + from the shell that launched it into the server process. Use this for every + value that must stay OUT of the file: **all secrets**, and any non-secret you + prefer to keep as a reference. The user exports the variable in the launching + shell (see [persisting-env-vars.md](persisting-env-vars.md)); Codex forwards it + on next launch. If a required forwarded variable is unset, the Agent Guard + fails at startup - confirm the export before restart. **Never write a raw + secret into `env`.** + +**Names are case-sensitive - copy the catalog input's `name` verbatim.** Every +`env_vars` entry, and every `env` key that carries a **catalog input** value, +MUST equal that input's `name` (from `--inspect`) character-for-character, +including case. (This does NOT apply to `_JF_ARGS` - it is a fixed Agent Guard +key, not a catalog input.) The Agent Guard matches the forwarded variable to the +upstream env var / header name exactly, so an uppercased or renamed variable is +silently dropped and the MCP starts with the value missing. e.g. mcp header input +is named `Authorization` → use `Authorization` (NOT `AUTHORIZATION`) in `env_vars` +and in the user's `export`. + +For a `Bearer` header the catalog exposes as a header input, forward it the same +way: have the user export the FULL header value under that exact name - e.g. +`export Authorization="Bearer "` - and list `Authorization` (verbatim +case) in `env_vars`. The prefix and secret both stay out of the file. + +Full entry shape - write the whole server as a **single `[mcp_servers.]` +table** with an inline `env = { … }` (do NOT split `env` into a separate +`[mcp_servers..env]` sub-table). `_JF_ARGS` is a literal in `env`; +secrets/refs go through `env_vars`: + +```toml +[mcp_servers.] +command = "npx" +args = ["--yes", "--registry", "", "@jfrog/agent-guard", "--server", ""] +env = { _JF_ARGS = "project=&mcp=", "" = "" } +env_vars = [""] +``` + +- `` is the sanitized slug from **Top-level key** (matches + `^[a-zA-Z0-9_-]+$`, needs no quoting); `mcp=` in `_JF_ARGS` keeps the exact + `spec.packageName`. +- **Include `--server `** to authenticate JFrog on Codex - it is the + default, and required when the user has multiple `jf` servers. It also keeps the + entry working if the user later adds more servers. (It can be omitted only when a + single `jf` server is configured, which the Agent Guard auto-resolves; see JFrog + credentials below.) `env_vars` here is only for the upstream MCP's own + secrets/inputs, never for JFrog credentials. +- Omit `env_vars` if there are no forwarded values; omit the extra `env` key if + `_JF_ARGS` is the only literal. Never emit an empty `--server`. +- **Always write the entry as one section** with the inline `env = { … }` above - + hand-write it, do NOT run `codex mcp add`. That command splits `env` into a + separate `[mcp_servers..env]` sub-table and cannot express `env_vars`. + +## JFrog credentials - from the `jf` config + +Codex does NOT forward ambient shell variables, so the Agent Guard reads its JFrog +credentials from the on-disk `jf` CLI config (which the Codex-launched process can +read). + +**Include `--server ` in `args` by default.** It reads that server's +URL + token from the `jf` config, is unambiguous, and keeps working if the user +later adds more servers. Resolve `` per the agent-guard-common +Pre-flight rules. + +`--server` can be **omitted only when exactly one `jf` server is configured** - in +that case the Agent Guard auto-resolves it. With **multiple** `jf` servers, +omitting `--server` fails: the Agent Guard cannot choose between them and does NOT +fall back to the `jf` default, so `--server` is required. (When in doubt, include +it.) + +**Codex exception to the shared rule.** [SKILL.md](../SKILL.md) treats `--server` +as conditional and permits dropping it on the `JFROG_URL`+token env path (see its +Step 4 Guardrails, "`--server` … drop it only on the `JFROG_URL`+token env +path"). **That env path does NOT apply on Codex** - Codex does not forward ambient +shell env to the server, so `JFROG_URL` / `JFROG_ACCESS_TOKEN` never reach the +Agent Guard. On Codex, therefore, do NOT authenticate JFrog via env-var +credentials; use `--server ` (or a single configured `jf` server) as +described above. If there is no usable `jf` server, ask the user to add one +(`jf c add `, or `jf login`) before continuing. + +If credentials cannot be resolved (no `--server` and either zero or multiple `jf` +servers), the entry fails to start with `connection closed: initialize response`. + +## Step 0 activation check under Codex's sandbox + +Codex runs shell commands in a sandbox with **no outbound network by default**, +and the skill's Step 0 check (`scripts/jfrog-agent-guard-check.mjs`) probes the +JFrog settings endpoint over the network. So the first run can report `Disabled: +settings endpoint unreachable (fetch failed)` even when the `jf` credentials are +valid - that is the sandbox blocking the request, NOT a missing or unreachable +server. On Codex, treat a first-run `unreachable (fetch failed)` as +**inconclusive, not a Disabled result** - do NOT apply the Step 0 "silently +abort" handling from +[agent-guard-activation.md](agent-guard-activation.md) yet. First re-run the SAME +check with network access (approve the escalated command, or run it outside the +sandbox); only treat the platform as unreachable if it STILL fails with network. +A follow-up `Enabled: via JF CLI config (server '')` confirms it was only the +sandbox. Credentials resolve from the on-disk `jf` config regardless - only the +reachability probe needs network. + +## Enable + +Codex servers are enabled by default (`enabled = true` is implicit) - there is no +per-server approval file to pre-write. Just make sure the entry is NOT +`enabled = false`. For a **project-scoped** entry, the directory must be trusted +or Codex ignores `.codex/config.toml` entirely. **Trust is the user's decision - +do NOT write `trust_level` yourself to self-approve a directory.** Ask the user to +accept Codex's trust prompt (or, only if they explicitly ask, they can set +`projects."".trust_level = "trusted"` in `~/.codex/config.toml`). + +## Restart + +Codex reads `config.toml` at startup and does not hot-reload it, and the agent +cannot restart Codex itself - **tell the user to start a new Codex session** (exit +and relaunch `codex`, or open a new session in the IDE extension) so the +added/removed entry and any newly exported `env_vars` take effect. + +## List installed + +`codex mcp list` for the configured servers with their auth status (one row per +server); `codex mcp get ` prints one server's resolved config. +For JFrog metadata, read the `[mcp_servers.*]` tables from `~/.codex/config.toml` +(user) and, if trusted, the project `.codex/config.toml`. Identify the package by +the `mcp=` value in each entry's `_JF_ARGS` (the table key is only a slug), and +show it as the display name. When reading an entry for metadata, use ONLY the +table key/slug, the `_JF_ARGS` values (`mcp=` / `project=`), and the `env_vars` +**names** - do NOT read, log, or display the `env` table's values (a user may have +placed a secret there despite the guidance above). An entry that does not appear +in `codex mcp list` is usually a TOML syntax error, an invalid server name (must +match `^[a-zA-Z0-9_-]+$`), or an untrusted project config. + +## Verify + +Run `/mcp` in the Codex TUI (or check the IDE extension's MCP view) and confirm +the server exposes the upstream MCP's **real tools**. `codex mcp list` shows the +server and its auth status but is NOT proof of working tools - the Agent Guard +proxy can report up with 0 upstream tools. + +Codex-specific signals to read correctly: +- **`Auth: Unsupported` is normal** for static-header and local MCPs - it + describes Codex's own OAuth support, not the upstream MCP. Judge by the tool + list. +- **An `enable__tools` tool is a normal Agent Guard gate**, not an error: + for MCPs that need sign-in or explicit enablement, the Agent Guard first + exposes this single tool; invoking it (e.g. "sign in to ``") runs the flow + and the upstream MCP's real tools then appear. Re-check `/mcp` afterward. +- If the **real tools never appear** (even after enabling / signing in), a + required input likely did not reach the server - most often an `env_vars` name + or shell export whose case does not match the catalog input `name` (see Value + reference), or a variable that was not exported in the launching shell. Fix it + and start a new session. A truly empty tool list = Failed → see the "0 tools" + troubleshooting in + [key-rules-and-troubleshooting.md](key-rules-and-troubleshooting.md). + +## Remove + +Find the target entry by matching `mcp=` in `_JF_ARGS`, then +`codex mcp remove ` (using that entry's table key), or delete +the whole `[mcp_servers.]` table by hand. Check BOTH scopes +(user `~/.codex/config.toml` and, if present, project `.codex/config.toml`) per +the SKILL.md Remove flow. There is no top-level `inputs`-style array to clean up. +Then start a new Codex session so the removed server stops loading. diff --git a/plugin/skills/jfrog-mcp-management/references/harness-common.md b/plugin/skills/jfrog-mcp-management/references/harness-common.md index ed57270..850a365 100644 --- a/plugin/skills/jfrog-mcp-management/references/harness-common.md +++ b/plugin/skills/jfrog-mcp-management/references/harness-common.md @@ -10,24 +10,36 @@ plus **exactly one** harness file; do NOT open the others. ## Step A — detect the harness and open ONE file -Detect from the environment (the `CLAUDECODE`/`CURSOR_*` signals below mirror -`../../jfrog/scripts/check-environment.sh` `detect_harness()`; the -`TERM_PROGRAM=vscode` editor hint is not in that script), then read only the -matching harness file. **Evaluate the rows top-to-bottom and take the FIRST -match** — order matters: Cursor is a VS Code fork and also sets -`TERM_PROGRAM=vscode`, so the Cursor row (checked first, on `CURSOR_*`) must win -before the VS Code `TERM_PROGRAM` row is considered. The VS Code harness file -targets the **VS Code editor** (Copilot MCP support), not the standalone GitHub -Copilot terminal CLI — the CLI (`COPILOT_CLI`) has no editor UI or `mcp.json`, -so it falls through to the Fallback section. If detection is not conclusive, ASK -the user which agent/editor they are in — do not guess, and do not read multiple -harness files. - -| Detected harness | Env signals | Read THIS file (and no other harness file) | +The `CLAUDECODE` / `CURSOR_*` / `CODEX_*` / `OPENCODE` signals below +mirror `../../jfrog/scripts/check-environment.sh` `detect_harness()`; the +`TERM_PROGRAM=vscode` editor hint is **not** in that script, and Devin is +**not** detected by the script. Each row's signal is **self-contained and +non-overlapping**, so detection does not depend on evaluation order. The VS +Code harness file targets the **VS Code editor** (Copilot MCP support), not +the standalone GitHub Copilot terminal CLI — the CLI (`COPILOT_CLI`) has no +editor UI or `mcp.json`, so it falls through to the Fallback section. + +1. Call `../../jfrog/scripts/check-environment.sh` and parse `tool=` from + the User-Agent line. When `tool` is `claude` or `cursor`, that matches the + Claude or Cursor row below — open that harness file. This call also + satisfies the Prerequisites environment check — capture/export + `JFROG_CLI_USER_AGENT` from it here too, rather than calling the script + again later. +2. Otherwise other `tool` values, `unknown`, or a missing `tool` are not enough + — **match this table**. Use how your system prompt identifies you plus any + environment variables that matching row lists. If row matches → open that file. + Unsure → step 3. Sure none apply → Fallback. +3. If detection is still not conclusive, ASK the user which agent/editor they + are in — do not guess, and do not read multiple harness files. + +| Detected harness | Signal (self-contained) | Read THIS file (and no other harness file) | | --- | --- | --- | -| Claude Code | `CLAUDECODE` or `CLAUDE_CODE_ENTRYPOINT` | [harness-claude.md](harness-claude.md) | -| Cursor | `CURSOR_AGENT` / `CURSOR_CLI` / `CURSOR_TRACE_ID` | [harness-cursor.md](harness-cursor.md) | -| VS Code editor | editor is VS Code (`TERM_PROGRAM=vscode`) **and no `CURSOR_*` var is set** | [harness-vscode.md](harness-vscode.md) | +| Claude Code | `CLAUDECODE` or `CLAUDE_CODE_ENTRYPOINT` env var | [harness-claude.md](harness-claude.md) | +| Codex | `CODEX_SANDBOX` / `CODEX_THREAD_ID` / `CODEX_CI` | [harness-codex.md](harness-codex.md) | +| Cursor | `CURSOR_AGENT` / `CURSOR_CLI` / `CURSOR_TRACE_ID` env var | [harness-cursor.md](harness-cursor.md) | +| OpenCode | `OPENCODE` | [harness-opencode.md](harness-opencode.md) | +| Devin Desktop | Your system prompt / system instructions identify you as **Devin** (Devin Desktop / Devin Local / Cognition). That alone is enough. Optionally confirm with `VSCODE_IPC_HOOK` set to the Devin Desktop IPC socket (full path), e.g. macOS: `~/Library/Application Support/Devin/-main.sock` — the expanded path contains `/Devin/`. The path alone is **not** enough. | [harness-devin.md](harness-devin.md) | +| VS Code editor | `TERM_PROGRAM=vscode` **and no `CURSOR_*` var is set** **and no `OPENCODE` var is set** **and no `CODEX_*` var is set** **and no `CLAUDECODE`/`CLAUDE_CODE_ENTRYPOINT` var is set** **and no `GEMINI_CLI` / `GOOSE_TERMINAL` / `COPILOT_CLI` var is set** **and** your system prompt / system instructions do **not** identify you as Devin | [harness-vscode.md](harness-vscode.md) | | anything else | none of the above | **Fallback** section below — no harness file exists | Once you know your harness, use ONLY these fields from its file: `Config files` @@ -39,10 +51,14 @@ harness-config" means: use the value from your one harness file. These do not vary; the harness file only overrides the pieces above. -**The Agent Guard entry** is always a stdio server invoking -`npx @jfrog/agent-guard`. `command`, `args` (and their order), and `_JF_ARGS` -are the same everywhere — only the wrapping top-level key and the value-reference -syntax come from your harness file. +**The Agent Guard entry** always invokes `npx @jfrog/agent-guard` with the same +argument tokens (in the same order) and the same `_JF_ARGS`. What varies per +harness is **how the entry is written** — the wrapping top-level key, the +value-reference syntax, and the entry *shape* itself (the transport field, and +whether `command`/`args` are separate). The JSON template below is the common +case; harnesses whose config is not JSON differ — e.g. **Codex** uses TOML with no +`type`, and **OpenCode** merges `command`+`args` into a single `command` array — so +**always follow your harness file's "Full entry shape" when it has one.** ```json { diff --git a/plugin/skills/jfrog-mcp-management/references/harness-devin.md b/plugin/skills/jfrog-mcp-management/references/harness-devin.md new file mode 100644 index 0000000..40f04c7 --- /dev/null +++ b/plugin/skills/jfrog-mcp-management/references/harness-devin.md @@ -0,0 +1,114 @@ +# Harness: Devin **Desktop** + +Devin Desktop-specific config for the `jfrog-mcp-management` skill. Read this +together with [harness-common.md](harness-common.md) (shared entry shape and +success criterion). You reached this file because Step A matched **Devin**: +your system prompt / system instructions identify you as Devin. You may +optionally confirm with `VSCODE_IPC_HOOK` under the Devin user-data dir (e.g. +`~/Library/Application Support/Devin/-main.sock`). The environment +script does not detect Devin. + +Devin Desktop is a VS Code-family Electron shell that runs the Cascade / Devin +Local agent. It stores MCP configuration in the Windsurf config file used by +the underlying platform. + +## Config files + +- **Default scope: user-level.** Personal, not committed, available across all + workspaces. **Prefer Windsurf** — the same file Cascade uses and that the + JFrog Desktop extension writes the `jfrog` MCP into: + - macOS/Linux: `~/.codeium/windsurf/mcp_config.json` + - Windows: `%APPDATA%\.codeium\windsurf\mcp_config.json` + + Create the parent directory first (`mkdir -p` / platform equivalent), then + create the file if missing: `{ "mcpServers": {} }`. Devin Local imports this + file when `read_config_from.windsurf` is not `false` in + `~/.config/devin/config.json` (default) — so one write serves Cascade and Local. +- **Exception — migrated native store:** If `~/.config/devin/mcp_config.json` + **already exists** (user accepted **Migrate MCP config** / Copy), Devin Local + uses that file instead of Windsurf import. For Local, merge entries **there** + and do **not** require `read_config_from.windsurf`. Cascade never reads the + native file — if the entry must also appear in Cascade, merge into Windsurf + as well. Prefer **Cancel** on migrate so both agents stay on Windsurf. +- **Project scope:** Not supported by Devin Desktop's Cascade / Windsurf config. +- Do not ask which scope unless the user brings it up. + +## Top-level key + +`mcpServers` + +## Value reference (env / secrets) + +`${env:VAR_NAME}`, resolved from the environment that launched Devin Desktop. +For `Bearer` headers: `"Bearer ${env:TOKEN}"`. Devin Desktop also supports +`${file:~/path/to/file}` to inline a file's trimmed contents. The user must +export the variable in the environment that launches Devin Desktop (see +[persisting-env-vars.md](persisting-env-vars.md)); values are picked up on +next launch. If a required `${env:VAR}` is unset the Agent Guard fails at +startup — confirm the export before restart. Never write a raw secret. + +## Enable + +Devin Desktop loads every non-disabled entry in `mcpServers` automatically on +window load; there is no per-server approval prompt to pre-approve. If the +entry carries `"disabled": true`, remove it so the server runs. Otherwise +nothing to do here. + +## Restart + +`Developer: Reload Window` (or fully quit and reopen Devin Desktop). Devin +Desktop re-reads `mcp_config.json` on window load and reconnects each server. + +## List installed + +Open the **MCP servers** panel (Cascade panel toolbar, or +`Devin Settings → Cascade → MCP Servers`), or **Open customizations** on a +Devin Local session — each configured server is listed with its live +connection state. Servers and their tools are also reachable via `@` in the +chat input. Do **not** use `/mcp` here: that slash command is Devin CLI only; +in Desktop `/` lists workflows, so `/mcp` can fuzzy-match a skill and mislead. +Confirm via the MCP servers panel / Open customizations, or by checking that +`` exists under `mcpServers` in the active store (Windsurf by default; +native `~/.config/devin/mcp_config.json` only when that file already exists — +see Config files). When reading the file, do not report secret values — env +**key names** only; never display resolved `${env:…}` or `${file:…}` contents. + +## Verify + +Before treating a missing server as Failed: confirm the entry is in the active +store (Windsurf by default; native only when that file already exists). For +Devin Local on Windsurf, also confirm `read_config_from.windsurf` is not +`false`. Skip that flag check when Local is on the native file. + +Ask which MCP servers are available, or open the MCP servers panel / Open +customizations, and confirm `` is listed and connected. Then ask the +agent to list that server's tools (or reach it via `@`); the server MUST +expose **at least one tool**. A connected indicator alone is NOT proof — the +Agent Guard proxy can report connected with 0 upstream tools. Empty tool +list = Failed → see the "0 tools" troubleshooting in +[key-rules-and-troubleshooting.md](key-rules-and-troubleshooting.md). + +On first connect without cached OAuth, Devin opens a browser to sign in; later +runs reuse stored credentials. Treat **Output → MCP** as authentication / +connection status only — never as a source of token values. Devin Local may +also prompt to approve each MCP tool call by default — grant the prompt before +treating an empty list as a failure. + +## Notes + +- Cascade always reads `~/.codeium/windsurf/mcp_config.json`. Devin Local + imports that same file when `read_config_from.windsurf` is enabled in + `~/.config/devin/config.json` (default). If Local is on Windsurf and that + flag is `false`, Local will not see Windsurf entries even though the file on + disk is unchanged. +- Some Devin Desktop builds prompt to copy Windsurf MCP config to + `~/.config/devin/mcp_config.json` (**Migrate MCP config**). Prefer **Cancel** + unless the user wants to migrate: once the native file exists, Local uses it + as its store (no Windsurf-import requirement) while Cascade continues to use + Windsurf only — installs then diverge unless you write both. +- OAuth `--login` caches tokens in `~/.jfrog/jfrogmcp.conf.json` (same as all + harnesses); removal cleanup of that file is the same everywhere. +- Devin Desktop is distinct from **Devin CLI** (the `devin` terminal agent): + the CLI has its own config at `.devin/config.json` / `.devin/config.local.json` + and is not covered by this harness file. CLI-only surfaces such as `/mcp` + do not apply here. diff --git a/plugin/skills/jfrog-mcp-management/references/harness-opencode.md b/plugin/skills/jfrog-mcp-management/references/harness-opencode.md new file mode 100644 index 0000000..816b810 --- /dev/null +++ b/plugin/skills/jfrog-mcp-management/references/harness-opencode.md @@ -0,0 +1,164 @@ +# Harness: OpenCode + +OpenCode-specific config for the `jfrog-mcp-management` skill. Read this together +with [harness-common.md](harness-common.md) (shared entry shape and success +criterion). You reached this file because the harness is OpenCode (`OPENCODE`, +set in the environment at startup). This targets all OpenCode surfaces (TUI, CLI, +Desktop, IDE, web) - they share one backend and the same `opencode.json`. + +> **How OpenCode stores the entry:** config is **JSON / JSONC** under the +> top-level **`mcp`** key; each server is a **`type: "local"`** entry whose +> **`command` is a single ARRAY** (executable + args combined - there is NO +> separate `args`); env vars go in an **`environment`** object; and value +> references use **`{env:VAR}`** (or `{file:/path}`). Write the entry using the +> JSON template in **Full entry shape** below. + +## Config files + +- **Default scope: user-level (global).** `~/.config/opencode/opencode.json` + (`.jsonc` also works) - personal, not committed, applies to every project. + Create if missing: `{ "mcp": {} }`. (`$OPENCODE_CONFIG`, if set, adds a custom + config file - merged after the global file and before project config - it does + NOT replace the global file; `$OPENCODE_CONFIG_DIR`, if set, adds a custom + config directory whose `opencode.json` / `.jsonc` is also loaded.) +- **Project:** `opencode.json` (or `.jsonc`) in the project root - shareable via + git. Use ONLY if the user says "for this project" / "commit" / "share with the + team". +- **Write to exactly one scope, never both.** Config files are merged; project + overrides global on conflicts. Do not ask which scope unless the user brings it + up. + +## Top-level key + +`mcp` - one entry per server: `mcp.`. Use `spec.packageName` +directly as the key; special characters (`.` `/` `@`) are fine because OpenCode +sanitizes the name (`[^a-zA-Z0-9_-]` → `_`) when it exposes tools as +`_`. + +## Value reference (env / secrets) + +`{env:VAR_NAME}` inside the `environment` object, substituted from OpenCode's +environment when it loads `opencode.json` (use `{file:/path}` to read a value +from a file instead). For `Bearer` headers: `"Bearer {env:TOKEN}"`. The user must +export the variable in the shell that launched OpenCode (see +[persisting-env-vars.md](persisting-env-vars.md)); values are picked up on next +launch. **Names are case-sensitive** - each `environment` key that carries a +catalog input MUST equal that input's `name` (from `--inspect`) +character-for-character, or the Agent Guard drops it and the MCP starts with the +value missing. Never write a raw secret - always a `{env:...}` / `{file:...}` +reference. + +Full entry shape (`command` is one array; `_JF_ARGS` is a literal in +`environment`; secrets/refs use `{env:...}`): + +```json +{ + "mcp": { + "": { + "type": "local", + "command": ["npx", "--yes", "--registry", "", "@jfrog/agent-guard", "--server", ""], + "enabled": true, + "environment": { + "_JF_ARGS": "project=&mcp=", + "": "{env:}" + } + } + } +} +``` + +- `"type": "local"` always - never `"remote"` or a top-level `"url"` (those + bypass the Agent Guard). +- `command` merges the common entry's `command` + `args` into ONE array, same + tokens in the same order; `--yes` and `--registry ` MUST precede + `@jfrog/agent-guard`. +- **Include `--server `** to authenticate JFrog - it is the default, + and required when the user has multiple `jf` servers; it also keeps the entry + working if the user later adds more servers. (It can be omitted only when a + single `jf` server is configured, which the Agent Guard auto-resolves; see JFrog + credentials below.) The `environment` block is only for the upstream MCP's own + secrets/inputs, never for JFrog credentials. +- **Always keep `environment` with `_JF_ARGS`** - it carries the project + + package identity the Agent Guard needs to route the request. Omit only optional + input keys; never drop `_JF_ARGS` or the whole `environment` object. + +## JFrog credentials - from the `jf` config + +**Include `--server ` by default.** It reads that server's URL + token +from the on-disk `jf` CLI config, is unambiguous, and keeps working if the user +later adds more servers. Resolve `` per the agent-guard-common +Pre-flight rules; never emit an empty `--server`. + +`--server` can be **omitted only when exactly one `jf` server is configured** - in +that case the Agent Guard auto-resolves it. With **multiple** `jf` servers, +omitting `--server` fails: the Agent Guard cannot choose between them and does NOT +fall back to the `jf` default, so `--server` is required. (When in doubt, include +it.) + +**OpenCode exception to the shared rule.** [SKILL.md](../SKILL.md) treats `--server` +as conditional and permits dropping it on the `JFROG_URL`+token env path (see its +Step 4 Guardrails, "`--server` … drop it only on the `JFROG_URL`+token env +path"). **That env path does NOT apply on OpenCode** - do NOT authenticate JFrog via env-var credentials, even though OpenCode would forward `JFROG_URL` / `JFROG_ACCESS_TOKEN` to the server. Use +`--server ` (or a single configured `jf` server) as described above. If +there is no usable `jf` server, ask the user to add one (`jf c add `, or +`jf login`) before continuing. + +If credentials cannot be resolved (no `--server` and either zero or multiple `jf` +servers), the entry fails to start and the server connects with no tools. + +## Enable + +Servers are enabled by default (`enabled: true` is implicit; only +`enabled: false` disables) - writing the entry is enough, there is no separate +approval file. To disable without deleting, set `enabled: false` in the entry and +edit the config file directly. + +## Restart + +OpenCode reads config and connects MCP servers at startup and does not hot-reload +edits - **tell the user to start a new OpenCode session** (exit and relaunch +`opencode`) so the added/removed entry and any newly exported `environment` +values take effect. + +## List installed + +`opencode mcp list` (alias `ls`) shows the configured servers with their +connection status. For JFrog metadata, read the `mcp` object from every config +scope listed under **Config files** above (global, `$OPENCODE_CONFIG`, +`$OPENCODE_CONFIG_DIR`, and project). Identify the package by the `mcp=` value in +each entry's +`environment._JF_ARGS`; the entry key is the display name. Parse only the `mcp` +section - do NOT print, log, or return the whole file or unrelated config values +(it may hold provider keys and personal settings). + +## Verify + +Confirm the server exposes the upstream MCP's **real tools** (they appear to the +agent as `_`). `opencode mcp list` shows connection +status, but a "connected" row is NOT proof - the Agent Guard proxy can report up +with 0 upstream tools. + +- **An `enable__tools` tool is a normal Agent Guard gate**, not an error: + for MCPs that need sign-in or explicit enablement, the Agent Guard first + exposes this single tool; invoking it (e.g. "sign in to ``") runs the flow + and the upstream MCP's real tools then appear. Re-check afterward. (OpenCode's + own `opencode mcp auth` is for `type: "remote"` OAuth servers only and does NOT + apply to this local Agent Guard entry.) +- If the **real tools never appear** (even after enabling / signing in), a + required input likely did not reach the server - most often an `environment` + name or shell export whose case does not match the catalog input `name` (see + Value reference), or a variable that was not exported in the launching shell. + Fix it and start a new session. A truly empty tool list = Failed → see the + "0 tools" troubleshooting in + [key-rules-and-troubleshooting.md](key-rules-and-troubleshooting.md). + +## Remove + +Find the target entry by matching `mcp=` in +`environment._JF_ARGS`, then delete the `mcp.` entry from whichever +config holds it - check every scope listed under **Config files** above (global, +`$OPENCODE_CONFIG`, `$OPENCODE_CONFIG_DIR`, and project). Hand-edit the file +directly (current builds have no `opencode mcp remove`), +touching only the target `mcp.` entry and leaving other config +values untouched and unprinted. There is no separate `inputs`-style array to +clean up. Then start a new OpenCode session so the removed server stops loading. diff --git a/plugin/skills/jfrog-mcp-management/references/persisting-env-vars.md b/plugin/skills/jfrog-mcp-management/references/persisting-env-vars.md index d6a27a5..1460227 100644 --- a/plugin/skills/jfrog-mcp-management/references/persisting-env-vars.md +++ b/plugin/skills/jfrog-mcp-management/references/persisting-env-vars.md @@ -1,10 +1,20 @@ # Persisting environment variables -Read this for **shell-based harnesses (Claude Code, Cursor)** when a Step 3 -input needs to be exported so its value reference (`${VAR}` for Claude Code, -`${env:VAR}` for Cursor) resolves — i.e. any secret, or a non-secret you chose -to keep out of the config as a reference. (VS Code does not use shell env for -this — it prompts for `inputs` values and stores them itself; skip this file.) +Read this for **shell-based harnesses** when a Step 3 input needs to be exported +so its value takes effect. How each harness picks up the exported variable: + +- **Claude Code** — a `${VAR}` reference in the config. +- **Cursor** — a `${env:VAR}` reference in the config. +- **Devin Desktop** — a `${env:VAR}` reference in the config. +- **Codex** — a variable name listed in the `env_vars` allow-list; Codex forwards + that named variable's value from the launching shell to the server (e.g. an env + var like `Authorization`). +- **OpenCode** — a `{env:VAR}` reference in the config `environment` (OpenCode + also forwards its ambient environment to local MCP servers). + +This applies to any secret, or a non-secret you chose to keep out of the config as +a reference. (VS Code does not use shell env for this — it prompts for `inputs` +values and stores them itself; skip this file.) These references resolve from the shell that launched the agent, so the variable has to be exported in that shell and persisted across relaunches. Don't rely on @@ -70,5 +80,6 @@ in play before writing to it: secret values into a config file. For secret values, instruct the user to add the line themselves (e.g. via `read -rs VAR_NAME && export VAR_NAME` for the current session) — you never see or type the value. -- After exporting, the user must **relaunch the agent** so `${VAR}` - references resolve. +- After exporting, the user must **relaunch the agent** so the exported value + takes effect — the harness picks it up on next launch (resolving `${VAR}` / + `${env:VAR}`, or forwarding the `env_vars`-listed variable on Codex). diff --git a/plugin/skills/jfrog-setup-package-managers/SKILL.md b/plugin/skills/jfrog-setup-package-managers/SKILL.md index bcd511e..9115f01 100644 --- a/plugin/skills/jfrog-setup-package-managers/SKILL.md +++ b/plugin/skills/jfrog-setup-package-managers/SKILL.md @@ -3,17 +3,12 @@ name: jfrog-setup-package-managers description: >- Use this skill when the user asks to set up, configure, bind, or connect a package manager (npm, pip, uv, pipenv, maven, gradle, go, docker, helm, ...) - to JFrog Artifactory via `jf setup` and - `.jfrog/local/package-resolution.json`; when a workspace manifest exists with - no matching binding entry; or when a session hook reports package-manager - config missing. Prefer uv for `uv.lock` / `[tool.uv]` — do not substitute pip - for uv when those signals exist; still bind pip when its own manifest (e.g. - `requirements.txt`) selects it. Yarn and Poetry are not part of Agent Package - Resolution zero-touch — bind only on explicit user request. Skip when the - binding already has the same repo key. Never pick a repo by discovery; use - resolver output only (unless the user names or asks to browse repos). On - unresolved or failed setup, ask with the failure verbatim — never switch - servers. + to JFrog Artifactory via `jf setup` and `.jfrog/local/package-resolution.json`; + when a workspace manifest exists with no matching binding entry; or when a + session hook reports package-manager config missing. Skip when the binding + already has the same repo key. Never pick a repo by discovery; use resolver + output only (unless the user names or asks to browse repos). On unresolved + or failed setup, ask with the failure verbatim — never switch servers. metadata: role: workflow --- diff --git a/plugin/skills/jfrog/SKILL.md b/plugin/skills/jfrog/SKILL.md index bde4918..d1d1702 100644 --- a/plugin/skills/jfrog/SKILL.md +++ b/plugin/skills/jfrog/SKILL.md @@ -18,7 +18,7 @@ compatibility: >- Requires jq on PATH. metadata: role: base - version: "0.21.0" + version: "0.22.0" --- # JFrog Skill diff --git a/plugin/skills/jfrog/scripts/check-environment.sh b/plugin/skills/jfrog/scripts/check-environment.sh index d58ad7e..adbd41d 100755 --- a/plugin/skills/jfrog/scripts/check-environment.sh +++ b/plugin/skills/jfrog/scripts/check-environment.sh @@ -144,9 +144,11 @@ EOF } # Detect the calling harness from environment signals. Output is one of: -# claude, cursor, gemini, goose, copilot, codex, unknown — or empty +# claude, cursor, gemini, goose, copilot, codex, opencode, unknown — or empty # string when no agent signal is present (direct CLI/CI invocation). # Naming matches the JFrog CLI's DetectExecutionContext() vocabulary. +# Devin Desktop is not detected here — see harness-common.md (agent identity +# + VSCODE_IPC_HOOK). The TERM_PROGRAM=vscode editor hint is also table-only. detect_harness() { if [[ -n "${CLAUDECODE:-}" || -n "${CLAUDE_CODE_ENTRYPOINT:-}" ]]; then echo "claude" @@ -160,6 +162,8 @@ detect_harness() { echo "copilot" elif [[ -n "${CODEX_CI:-}" || -n "${CODEX_THREAD_ID:-}" || -n "${CODEX_SANDBOX:-}" ]]; then echo "codex" + elif [[ -n "${OPENCODE:-}" ]]; then + echo "opencode" elif [[ -n "${AGENT:-}" || -n "$MODEL_SLUG" ]]; then # Agent invoked us but we can't name it. echo "unknown"