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 .cursor-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"metadata": {
"description": "JFrog Platform plugins for Cursor",
"version": "0.5.5",
"version": "0.5.6",
"pluginRoot": "plugins"
},
"plugins": [
Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/validate-inject-instructions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright (c) JFrog Ltd. 2026
Comment thread
MatanEden1 marked this conversation as resolved.
# Licensed under the Apache License, Version 2.0
# https://www.apache.org/licenses/LICENSE-2.0

name: Validate hook injection

on:
pull_request:
branches: [main]
paths:
- "plugins/jfrog/scripts/inject-instructions.mjs"
- "plugins/jfrog/templates/jfrog-mcp-management.md"
- "plugins/jfrog/hooks/hooks.json"
- "plugins/jfrog/.cursor-plugin/plugin.json"
- "scripts/validate-hook-injector.mjs"
workflow_dispatch:

permissions:
contents: read

jobs:
validate:
name: Validate hook injection
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Run injector validation
run: node scripts/validate-hook-injector.mjs
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Before installing, make sure you have:
- **Node.js** (≥ 14) — with `npx` on your `PATH`.
- **Skill runtime requirements** — `jf` CLI, `jq`, and `curl` on `PATH`, plus a configured JFrog instance. For the minimum versions, see the upstream skills [`Requirements`](https://github.com/jfrog/jfrog-skills/blob/v0.11.0/README.md#requirements). Configure the CLI with `jf config add` — see [Authentication](#authentication).
- **JFrog Platform access** (optional) — If you want to use the Agent Guard feature, your JFrog subscription needs to include the AI Catalog entitlement. Contact your JFrog account team if you're unsure whether it's enabled.
- **JFrog CLI ≥ 2.105.0** (optional) — If you want the Agent Guard hook to auto-resolve credentials/server ID from the JFrog CLI instead of `JFROG_PLATFORM_URL`/`JFROG_ACCESS_TOKEN` env vars. Older CLIs don't support the `--format` flag used by `jf config show`/`jf config export` for this.
- **JFrog project** (optional) — If you want to use the Agent Guard feature.

---
Expand Down
2 changes: 1 addition & 1 deletion plugins/jfrog/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "jfrog",
"displayName": "JFrog Platform",
"version": "0.5.5",
"version": "0.5.6",
"description": "JFrog Platform integration with MCP, security skills, supply-chain best practices, and JFrog Agent Guard governance for adding, removing, and listing MCP servers.",
"author": {
"name": "JFrog",
Expand Down
1 change: 1 addition & 0 deletions plugins/jfrog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ JFrog Platform integration for Cursor — artifact management, security scanning
- Toggle the **MCP Server** option ON and save.
3. Set the `JFROG_PLATFORM_URL` environment variable to your JFrog instance (e.g., `mycompany.jfrog.io`).
4. **JFrog CLI** (`jf`) is used by the skills for authentication and REST/GraphQL API operations. If missing, the agent will attempt to install it. You can also install manually via `brew install jfrog-cli` or the [official install script](https://jfrog.com/help/r/jfrog-cli/install-the-jfrog-cli).
5. **JFrog CLI ≥ 2.105.0** (optional) — required if you want the Agent Guard hook to auto-resolve credentials/server ID from the JFrog CLI instead of `JFROG_URL`/`JFROG_ACCESS_TOKEN` env vars. Older CLIs don't support the `--format` flag used by `jf config show`/`jf config export` for this.

CLI authentication options: run `jf login` for browser-based setup, or set the `JFROG_ACCESS_TOKEN` environment variable. MCP-based workflows authenticate via **OAuth** and require no additional configuration.

Expand Down
55 changes: 48 additions & 7 deletions plugins/jfrog/scripts/inject-instructions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Licensed under the Apache License, Version 2.0
// https://www.apache.org/licenses/LICENSE-2.0

import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import path from "node:path";
import process from "node:process";
Expand All @@ -24,17 +25,57 @@ const forceDisabled =
const forceEnabled =
env("JF_AGENT_GUARD_FORCE_ENABLE") === "true";

async function isAgentGuardEnabledViaSettings() {
// Resolve {baseUrl, token}: environment variables (JFROG_URL/JFROG_ACCESS_TOKEN,
// or legacy JF_*) are checked first; if either is missing, fall back to the
// JFrog CLI's default configured server via `jf config export`. Returns null
// when neither source yields usable credentials.
function resolveCredentials() {
const baseUrl = env("JFROG_URL", "JF_URL");
const token = env("JFROG_ACCESS_TOKEN", "JF_ACCESS_TOKEN");
if (!baseUrl) {
debug("JFROG_URL/JF_URL is not set; skipping settings check");
return false;
if (baseUrl && token) {
debug("Resolved credentials from environment variables");
return { baseUrl, token };
}

// `jf config export` emits the default server as a base64-encoded JSON token.
let configToken;
try {
configToken = execFileSync("jf", ["config", "export"], {
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
timeout: 2000,
}).trim();
} catch (error) {
debug(`'jf config export' failed (jf not on PATH or no server configured): ${error.message}`);
return null;
}

// The token is a base64-encoded JSON blob containing the server's url,
// accessToken, and serverId — decode and validate it before using it.
let cfg;
try {
cfg = JSON.parse(Buffer.from(configToken, "base64").toString("utf8"));
} catch (error) {
debug(`Could not decode the jf Config Token: ${error.message}`);
return null;
}

if (!cfg?.url || !cfg?.accessToken) {
debug("jf Config Token did not contain a usable url + accessToken");
return null;
Comment thread
MatanEden1 marked this conversation as resolved.
}
if (!token) {
debug("JFROG_ACCESS_TOKEN/JF_ACCESS_TOKEN is not set; skipping settings check");

debug(`Resolved credentials via 'jf config export' (serverId: ${cfg.serverId ?? "<unknown>"})`);
return { baseUrl: cfg.url, token: cfg.accessToken };
}

async function isAgentGuardEnabledViaSettings() {
const credentials = resolveCredentials();
if (!credentials) {
debug("No JFrog credentials resolved; skipping settings check");
return false;
}
const { baseUrl, token } = credentials;

const url =
baseUrl.replace(/\/+$/, "") +
Expand All @@ -43,7 +84,7 @@ async function isAgentGuardEnabledViaSettings() {
debug(`Fetching agent guard setting from ${url}`);

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
const timeout = setTimeout(() => controller.abort(), 4000);
try {
const response = await fetch(url, {
method: "GET",
Expand Down
69 changes: 33 additions & 36 deletions plugins/jfrog/templates/jfrog-mcp-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ below instead.

**Registry URL**: Wherever `<REGISTRY_URL>` appears below, substitute
the value of the `JFROG_AGENT_GUARD_REPO` environment variable if it
is set. Otherwise use
is set. Otherwise, use
`https://releases.jfrog.io/artifactory/api/npm/coding-agents-npm/`.

**Pre-flight (applies to every agent guard command —
Expand All @@ -30,22 +30,22 @@ is set. Otherwise use
resolves, STOP and ask — NEVER guess, NEVER assume `default`,
NEVER invent projects.

- **`<SERVER_ID>` is auto-resolvable.** Resolve via Step 1's server
chain: existing `mcpServers` entries (value after `--server` in
`args`) → `~/.jfrog/jfrog-cli.conf.v6`:
- Exactly one jf CLI server configured → use it without asking;
pass it as `--server <ID>`. The agent guard would auto-resolve to the same
value if `--server` were omitted, but we pass it explicitly for
clarity and forward-compatibility.
- `JFROG_URL` + `JFROG_ACCESS_TOKEN` set → use it without asking;
The agent guard will pick them up from the environment variables when called.
- Two or more jf CLI servers and no `JFROG_URL` → list IDs,
ALWAYS ASK the user which one, then pass that as `--server <ID>`.
ALWAYS prefer environment variables when set over asking.
NEVER guess one server.
- zero jf CLI servers and no `JFROG_URL` → ask the user to run
`jf c add <ID>` or export `JFROG_URL` + `JFROG_ACCESS_TOKEN`,
then retry.
- **`<SERVER_ID>` is auto-resolvable.** Resolve in order, stop at the
first match:
1. An existing `mcpServers` entry's `--server <ID>` (project or user
Comment thread
yanivt-jfrog marked this conversation as resolved.
config) — reuse it.
2. `JFROG_URL` + `JFROG_ACCESS_TOKEN` set in the env — use them and do
NOT pass `--server` (the agent guard reads the env directly).
3. List configured servers with the jf CLI — `jf config show --format=json`
Comment thread
MatanEden1 marked this conversation as resolved.
Comment thread
MatanEden1 marked this conversation as resolved.
(do NOT parse `~/.jfrog/jfrog-cli.conf.v6`; the CLI masks tokens, so
its output is safe). Exactly one → use it; two or more → use the one
with `"isDefault": true`; if none is marked default → ASK the user
which one. Then pass `--server <ID>`.
4. None of the above → ask the user to run `jf c add <ID>` or export
`JFROG_URL` + `JFROG_ACCESS_TOKEN`, then retry.

When you resolved the ID from a jf CLI config, always pass it as
`--server <ID>`; when using env vars, never pass `--server`.
- The commands need network access and MUST be run with `full_network`
permissions when run in a sandbox. Otherwise `Forbidden` errors will
be thrown.
Expand All @@ -60,11 +60,11 @@ STOP — do NOT run the command with guesses.
"add an MCP", "what can I install" — your FIRST action is to show
them the catalog so they can pick:

1. Resolve server (Server ID`<SERVER_ID>` or URL `JFROG_URL`)
1. Resolve server (Server ID `<SERVER_ID>` or URL `JFROG_URL`)
and `<PROJECT>` per the Pre-flight rule at the top of this document.
Server: auto-use the single jf CLI configs serverId as the server ID
or the `JFROG_URL` env var as the URL if unambiguous; only ask when
there are multiple or no jf configs and not env vars.
there are multiple or no jf configs and no env vars.
Project: Ask unless `JF_PROJECT` is set, or it's already in an
existing `mcpServers` entry.
2. Run "Listing MCPs > Available to install" with that server +
Expand All @@ -90,22 +90,20 @@ unless absolutely necessary:
agent guard can resolve credentials from these directly;
DO NOT pass `--server` as that would make the agent guard try to
parse the server details from the jf cli configuration.
3. Else read `~/.jfrog/jfrog-cli.conf.v6`
(`%USERPROFILE%\.jfrog\jfrog-cli.conf.v6` on Windows) via a
terminal command (file-search skips hidden dirs)
NEVER print the full file contents as it can contain secrets.
Use the serverId subkeys::
3. Else list configured servers with the jf CLI — run
`jf config show --format=json` (do NOT parse
`~/.jfrog/jfrog-cli.conf.v6` yourself; the CLI masks tokens, so its
output is safe to read). From the result:
- exactly one server → use it without asking.
- two or more → list the `serverId`s and ASK the user which one.
- two or more → use the one with `"isDefault": true`; if none is
marked default, list the `serverId`s and ASK the user which one.
4. Else (file missing, empty, or unreadable, and no `JFROG_URL`)
ask the user to either run `jf c add <ID>` or export
`JFROG_URL` + `JFROG_ACCESS_TOKEN`, then retry.

NEVER try multiple servers — pick one. Once chosen, pass it
If a server from the jf cli configuration is supposed to be used:
Always explicitly as `--server <ID>` in every agent guard invocation.
Otherwise, if environment variables for `JFROG_URL` and `JFROG_ACCESS_TOKEN`
are used: Do NOT pass `--server <ID>`
NEVER try multiple servers — pick one. When you resolved the ID from a
jf CLI config, always pass it as `--server <ID>` in every agent guard
invocation; when using env vars, never pass `--server`.

**Project**

Expand All @@ -131,9 +129,8 @@ not call `--inspect` — go to "Listing MCPs > Available to install"
instead, show the catalog, have them pick, then come back to Step 2
with the chosen name.

Once you have a name, you must fetch its live details.

Run EXACTLY this command — no Fetch/WebFetch, no custom curl/Python, no direct JFrog API calls:
Once you have a name, run a SINGLE command — no Fetch/WebFetch, no
custom curl/Python, no direct JFrog API calls:

```
npx --yes \
Expand Down Expand Up @@ -403,9 +400,9 @@ the display name.
## Troubleshooting

- **`ready` but 0 tools (empty `mcps/<key>/tools/` after a
Command Palette `Developer: Reload Window`)** — agent guard proxy
started, upstream MCP did not. The top-level `ready` label is
misleading here. NEVER report success when there are 0 tools.
Command Palette `Developer: Reload Window`)** — agent guard proxy
started, upstream MCP did not. The top-level `ready` label is
misleading here. NEVER report success when there are 0 tools.
1. Open Cursor's MCP / Output panel for the
agent guard stderr; diagnose by MCP type:
- **OAuth (remote)** — re-run Step 5 (`--login`); refresh token
Expand Down
Loading
Loading