AX-1694 — Align VS Code copilot-instructions with Claude/Cursor agent-guard docs#21
AX-1694 — Align VS Code copilot-instructions with Claude/Cursor agent-guard docs#21MatanEden1 wants to merge 9 commits into
Conversation
…-guard docs
Bring the VS Code MCP management instructions up to the structure of the
updated Claude and Cursor agent-guard docs, while keeping VS Code-native
mechanics:
- Add Pre-flight section (live-execution/no-local-resolution rule,
mandatory <PROJECT>, auto-resolvable <SERVER_ID>, network/sandbox note)
- Catalog-first "Adding an MCP" flow; numbered Steps 1-5 with explicit
start+verify (4a) and OAuth login (5)
- Keep VS Code secrets model: top-level `inputs` + `${input:id}` +
keychain prompt-on-first-start (NOT env-var export)
- Routing table for Listing MCPs; available vs installed
- New Managed Settings section: block non-agent-guard installs and
removal of protected MCPs
- Explicit project re-sync on update/switch; orphaned-input cleanup on
removal; expanded Key Rules and Troubleshooting
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ror project-switch priority into Pre-flight - "live execution" exemption now says "local config files" (covers both workspace .vscode/mcp.json and the user-level MCP config) instead of naming only the workspace file - mirror the explicit project-switch priority into the Pre-flight <PROJECT> bullet so it also governs listing/inspecting, not just writes Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Regression check vs the Claude/Cursor sources found the only dropped canonical detail: both gate --login on a remote section *with type:"http"*. VS Code had generalized it to "a remote section", which could fire --login for non-http remotes. Restore the qualifier. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
YoniMelki
left a comment
There was a problem hiding this comment.
Review: requesting changes
Reviewed against the canonical Claude (claude-plugin/templates/jfrog-mcp-management.md) and Cursor (cursor-plugin/plugins/jfrog/templates/jfrog-mcp-management.md) templates, since the goal of this PR is cross-plugin alignment.
The alignment work itself is solid — structure, command blocks, Pre-flight resolution chains, and Key Rules all track the other two templates, and the VS Code-native mechanics (servers / inputs / ${input:...} keychain model, CodeLens, MCP: List Servers) are preserved correctly. Nice work.
Requesting changes for the items below — details in the inline comments.
Must fix (this file):
- Trailing whitespace, plus a
. For HTTP→.For HTTPmissing-space regression in the Step 4inputsrules.
Needs a decision / release step:
- Version bump: this changes the template that ships to users, but
marketplace.json(currently1.0.2) isn't bumped. Per our release convention vscode-plugin bumpsmarketplace.jsononly — please bump it so the new instructions actually ship. - A few additions here are ahead of / diverge from Claude & Cursor (the
## Managed Settingssection, the<PROJECT>re-syncException:clause, and the richer Step 2 error handling). Each is good on its own, but for true alignment we should either back-port them to the other two plugins or mark them as intentional VS-Code-only. Please also correct the PR description line about VS Code 'missing ... managed-settings enforcement' — it's actually ahead there.
Confirmed separately: the registry env var JFROG_AGENT_GUARD_REPO is correct (matches Claude). Cursor's JFROG_MCP_GATEWAY_REPO is the stale outlier and can be fixed in a separate change.
| `JF_PROJECT` env var → ASK the user. Exception: when the user | ||
| EXPLICITLY asks to switch project or re-sync the configuration, the | ||
| project they name (or the current `JF_PROJECT`) takes priority over | ||
| existing `_JF_ARGS`. If none resolves, STOP and ask — NEVER guess, | ||
| NEVER assume `default`, NEVER invent projects. |
There was a problem hiding this comment.
This Exception: clause — an explicit switch/re-sync overriding existing _JF_ARGS — is a good addition, but it does not exist in the Claude or Cursor <PROJECT> Pre-flight bullets. Since this PR's goal is cross-plugin alignment, let's either back-port it to claude-plugin/cursor-plugin or explicitly note it as deliberate VS-Code-only behavior.
| 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>` |
There was a problem hiding this comment.
This server-resolution block reads as broken prose ("...Once chosen, pass it / If a server from the jf cli configuration is supposed to be used: / Always explicitly as --server <ID>..."). It's copied verbatim from the Claude/Cursor templates so it doesn't hurt alignment, but since all three are being touched it's worth rewording consistently, e.g.: "Once chosen: if using a jf CLI server, always pass it explicitly as --server <ID> in every invocation; if using JFROG_URL + JFROG_ACCESS_TOKEN instead, do NOT pass --server."
| On non-zero exit (typo, MCP not in catalog, network error, etc.), | ||
| show the error verbatim, then run `--list-available` (see "Listing | ||
| MCPs") so the user can pick a valid name and retry. If the failure is | ||
| because the MCP is **not allowed in the current project**, say so | ||
| conversationally and name the project — do NOT retry against a | ||
| different project. | ||
|
|
||
| **If the user did NOT specify a name** (e.g. "what can I install?"), | ||
| run `--list-available` instead (see "Listing MCPs" below). | ||
| If the user gave a name that is NOT in the catalog (typo, near-miss), | ||
| do NOT auto-install or guess. Run `--list-available`, present the | ||
| nearest matching names, and ask the user to confirm which one they | ||
| meant before proceeding. |
There was a problem hiding this comment.
The "not allowed in the current project" and "name not in catalog / near-miss" handling here is richer than the Claude/Cursor Step 2 (which only says "show the error verbatim, then run --list-available"). Good content — flagging only because it widens the gap with the two templates this PR aims to align with. Consider back-porting for consistency.
| `description` field. If the catalog leaves the description as an empty | ||
| string `""`, construct a brief context-appropriate description instead. | ||
| - Reference the input from `env` with `"${input:<id>}"`.For HTTP |
There was a problem hiding this comment.
Two small fixes in this block:
- Line 251 ends with a trailing space (after
empty). - Line 253:
"${input:<id>}".For HTTP is missing a space after the period (.For→. For). The pre-rewrite text had the space; it regressed in this rewrite.
| ## Managed Settings | ||
|
|
||
| Managed settings (`managed-settings.json`) can enforce | ||
| organization-level policy. When managed settings are in effect, they | ||
| override everything below and CANNOT be bypassed: | ||
|
|
||
| - **Agent-guard-only installation is enforced.** If the user asks to | ||
| install an MCP by any other means (`npm install`, `pip`, `docker`, | ||
| hand-editing `.vscode/mcp.json` with a direct `url`/`http`/`sse` | ||
| entry, etc.), BLOCK it immediately, run nothing, and respond | ||
| conversationally that all MCP installations must go through the | ||
| JFrog Agent Guard. | ||
| - **Protected MCPs cannot be removed.** If the target of a removal is | ||
| protected by managed settings, BLOCK the uninstall immediately and | ||
| respond conversationally that the MCP is protected by managed | ||
| settings and cannot be removed. Do not edit any config file. |
There was a problem hiding this comment.
This entire ## Managed Settings section is net-new and is not present in the Claude or Cursor templates today (Claude references managed-settings.json only in passing under Listing/Troubleshooting; Cursor not at all). So the PR description's claim that VS Code was "missing ... managed-settings enforcement while the other two plugins moved ahead" isn't accurate — VS Code is actually ahead here. Two asks: (1) correct that sentence in the PR description, and (2) decide whether this section should be back-ported to Claude/Cursor so all three stay genuinely aligned.
Address YoniMelki's review on PR #21: - Remove the VS-Code-only <PROJECT> switch/re-sync Exception clause from the Pre-flight bullet (not present in Claude/Cursor). - Reword the broken server-resolution prose into a single clear sentence. - Fix two regressions in the Step 4 inputs rules: trailing whitespace and a missing space ("${input:<id>}".For -> . For). - Remove the net-new Managed Settings section (and its cross-reference in Removing an MCP); not present in Claude/Cursor. Step 2's richer not-allowed/near-miss handling is kept intentionally (to be back-ported to Claude/Cursor for alignment). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…js injector Consolidate ensure-instructions.sh and ensure-instructions.ps1 into a single inject-instructions.mjs that works on all platforms. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ions Add resolveCredentials() with strict priority chain: --server flag, env vars, CLI config default profile, and single-profile fallback. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
YoniMelki
left a comment
There was a problem hiding this comment.
Re-review (head 3585b81): still requesting changes
Thanks for the updates. Confirmed resolved from the previous round: the . For HTTP space regression and the trailing whitespace in the template are fixed, and the project-switch priority is now mirrored into Pre-flight.
The scope grew to replace the shell scripts with inject-instructions.mjs, and that new file has blocking issues — details inline. Summary:
Blocking
inject-instructions.mjsreads the wrong template filename (jfrog-mcp-management.mdvs this repo'scopilot-instructions.md) and fails silently → nothing is ever injected.- The injector no longer writes
.github/copilot-instructions.md, which is the file VS Code / GitHub Copilot actually reads — only the Claude-CodeadditionalContextis emitted now. Need confirmation this delivery-model change is intentional.
Should fix
- Asymmetric force-flag env-var names (disable has a leading
_, enable doesn't). --serverresolution is dead code, since the hook invokes the script with no args.- New per-session network call on SessionStart (minor; please note it in the PR).
Process
marketplace.jsonis1.0.3on bothmainand this branch, so the PR doesn't bump it — confirm these changes ship under a fresh version (if1.0.3is already published, this needs1.0.4).- The PR description is now stale: it doesn't mention the script→Node-injector replacement (the largest code change here), and still claims VS Code was 'missing … managed-settings enforcement while the other two plugins moved ahead' (VS Code is actually ahead). Please update both.
| try { | ||
| template = readFileSync( | ||
| path.join(root, "templates", "jfrog-mcp-management.md"), | ||
| "utf8", | ||
| ); | ||
| } catch { | ||
| process.exit(0); | ||
| } |
There was a problem hiding this comment.
Critical — wrong template filename; the feature silently injects nothing. This reads templates/jfrog-mcp-management.md, but the only template in this repo is templates/copilot-instructions.md (jfrog-mcp-management.md is the Claude/Cursor filename — this looks copy-pasted from the Claude plugin). Because the read is wrapped in catch { process.exit(0) }, the failure is silent: the read throws, the script exits 0, and no instructions are ever injected. Change line 170 to "copilot-instructions.md".
| process.stdout.write( | ||
| JSON.stringify({ | ||
| hookSpecificOutput: { | ||
| hookEventName: "SessionStart", | ||
| additionalContext: template, | ||
| }, | ||
| }), | ||
| ); |
There was a problem hiding this comment.
Major — this no longer writes .github/copilot-instructions.md. The old ensure-instructions.sh copied the template into .github/copilot-instructions.md in the workspace (the file VS Code / GitHub Copilot actually reads) and emitted this additionalContext. This injector only emits additionalContext, which is a Claude Code session mechanism that Copilot does not consume — so for a Copilot-instructions plugin, the instructions no longer reach Copilot. If the delivery model intentionally pivoted to Claude-Code-session-only injection, please call that out in the PR/Jira; otherwise the file-write half needs to be restored.
| const forceDisabled = | ||
| env("_JF_AGENT_GUARD_FORCE_DISABLE", "_JF_MCP_GATEWAY_FORCE_DISABLE") === "true"; | ||
| const forceEnabled = | ||
| env("JF_AGENT_GUARD_FORCE_ENABLE", "JF_MCP_GATEWAY_FORCE_ENABLE") === "true"; |
There was a problem hiding this comment.
Likely bug — asymmetric env-var naming. The disable flags carry a leading underscore (_JF_AGENT_GUARD_FORCE_DISABLE / _JF_MCP_GATEWAY_FORCE_DISABLE) but the enable flags don't (JF_AGENT_GUARD_FORCE_ENABLE / JF_MCP_GATEWAY_FORCE_ENABLE). A user setting JF_AGENT_GUARD_FORCE_DISABLE (the natural guess, no underscore) would silently have no effect. Confirm the underscore is intentional, or align the two.
| function getServerFlagValue() { | ||
| const args = process.argv; | ||
| for (let i = 0; i < args.length; i++) { | ||
| if (args[i].startsWith("--server=")) { | ||
| return args[i].split("=")[1]; | ||
| } | ||
| if (args[i] === "--server" && i + 1 < args.length) { | ||
| return args[i + 1]; | ||
| } | ||
| } | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Dead code as invoked. hooks.json runs node ".../inject-instructions.mjs" with no arguments, so process.argv never contains --server; this always returns null and Priority 1 of resolveCredentials (lines 64–75) can never fire. Either thread --server through from the hook, or drop the branch — as-is it misrepresents the actual resolution order.
| async function isGatewayEnabledViaSettings() { | ||
| const credentials = resolveCredentials(); | ||
| if (!credentials) { | ||
| debug("No credentials resolved; skipping settings check"); | ||
| return false; | ||
| } | ||
| const { baseUrl, token } = credentials; | ||
| const url = | ||
| baseUrl.replace(/\/+$/, "") + | ||
| "/ml/core/api/v1/administration/account-settings/mcp_gateway_plugin_enabled"; | ||
|
|
||
| debug(`Fetching gateway setting from ${url}`); | ||
|
|
||
| const controller = new AbortController(); | ||
| const timeout = setTimeout(() => controller.abort(), 5000); |
There was a problem hiding this comment.
Minor — new per-session network dependency. Every SessionStart now makes this call (5s timeout, fails closed). The old script made no network call. The behavior is reasonable, but worth noting in the PR: an unreachable/slow JFrog server can add up to ~5s to session start, and instructions won't load when offline.
- Fix template filename to copilot-instructions.md (was injecting nothing) - Restore .github/copilot-instructions.md write for Copilot delivery - Align force-enable env vars with underscore-prefixed convention - Remove unreachable --server resolution branch and fix precedence docs - Lower gateway settings-check timeout from 5s to 3s Co-authored-by: Cursor <cursoragent@cursor.com>
--login launches the system browser and runs a local OAuth callback server, so a sandboxed run needs unrestricted permissions. Co-authored-by: Cursor <cursoragent@cursor.com>
Jira Ticket Path: https://jfrog-int.atlassian.net/browse/AX-1694
The VS Code
copilot-instructions.mdtemplate had drifted from the updated Claude and Cursor agent-guard instructions — it was missing the newer structure (Pre-flight rules, catalog-first flow, managed-settings enforcement) while the other two plugins moved ahead. This aligns the VS Code template with the same structure and rules, while preserving the VS Code-native mechanics that intentionally differ from Claude/Cursor.Changes
<PROJECT>, auto-resolvable<SERVER_ID>, sandbox/network note), catalog-first "Adding an MCP" flow, and numbered Steps 1–5 with explicit start+verify (4a) and OAuth login (5).inputs+${input:...}substitution + OS-keychain prompt-on-first-start (not shell env-var export).type: "http"qualifier on the Step 5 OAuth condition to match Claude/Cursor.Tests
Matan ran manual tests, and the results look good.
🤖 Generated with Claude Code