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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The JFrog plugin provides the following capabilities, grouped by component:

| Component | Feature | Description |
| --- | --- | --- |
| **MCP** | JFrog MCP server | Remote JFrog MCP server auto-attached to every session via `.mcp.json` at `${JFROG_URL}/mcp` (OAuth, no API keys). |
| **Hook** | Agent Guard | Copilot manage MCPs through the JFrog Agent Guard. Through it you can discover, install, configure, update, and remove MCP servers from the JFrog AI Catalog approved for your project, and authenticate to remote HTTP MCPs via OAuth, API key, or bearer token. |

---
Expand Down Expand Up @@ -82,7 +83,7 @@ VS Code opens, prompts you to install the plugin, and asks you to **Trust** the

| Variable | Description |
| --- | --- |
| `JFROG_URL` | Your JFrog platform URL, e.g. `https://mycompany.jfrog.io` |
| `JFROG_URL` | Your JFrog platform URL, e.g. `https://mycompany.jfrog.io` (no trailing `/`) |
| `JFROG_ACCESS_TOKEN` | Your JFrog access token |

### 2. Configure the JFrog CLI
Expand Down
2 changes: 1 addition & 1 deletion marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{
"name": "jfrog",
"description": "JFrog Platform integration with MCP, security skills, and supply-chain best practices",
"version": "1.0.4",
"version": "1.0.5",
"source": "plugin",
"categories": ["security", "artifact-management", "supply-chain", "devops", "mcp", "mlops", "agent-guard", "ai-catalog"],
"platforms": ["darwin", "linux", "windows"],
Expand Down
2 changes: 1 addition & 1 deletion plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "jfrog",
"description": "JFrog Platform integration with MCP, security skills, and supply-chain best practices",
"version": "1.0.4",
"version": "1.0.5",
"author": { "name": "JFrog", "url": "https://jfrog.com" },
"hooks": "hooks/hooks.json"
}
8 changes: 8 additions & 0 deletions plugin/.mcp.json
Comment thread
MatanEden1 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"jfrog": {
"type": "http",
"url": "${JFROG_URL}/mcp"
}
Comment thread
MatanEden1 marked this conversation as resolved.
}
}
31 changes: 29 additions & 2 deletions plugin/scripts/inject-instructions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { fileURLToPath } from "node:url";

// Logs go to stderr; stdout is reserved for the hook JSON payload.
const debugEnabled = process.env.JF_AGENT_GUARD_DEBUG === "true";
const log = (message) => console.error(`[jfrog-agent-guard] ${message}`);
const log = (message) => console.error(message);
const debug = (message) => {
if (debugEnabled) log(message);
};
Expand Down Expand Up @@ -112,13 +112,40 @@ if (forceDisabled) {
debug("Force-disable flag is set.");
process.stdout.write("{}");
process.exit(0);
} else if (forceEnabled) {
}

// Validate JFROG_URL early to surface misconfigurations before the MCP server
// attempts to connect and fails with a confusing DNS or double-slash error.
if (!process.env.JFROG_URL) {
const missingUrlWarning = "WARNING: JFROG_URL is not set. The JFrog MCP server will be unreachable — set JFROG_URL to your Artifactory base URL (e.g. https://mycompany.jfrog.io) and restart.";
log(missingUrlWarning);
process.stdout.write(JSON.stringify({
hookSpecificOutput: {
hookEventName: "SessionStart",
additionalContext: `IMPORTANT: Immediately tell the user: "${missingUrlWarning}"`,
},
}));
process.exit(0);
} else if (process.env.JFROG_URL.endsWith("/")) {
const trailingSlashWarning = "WARNING: JFROG_URL has a trailing slash. This produces a double-slash in the MCP URL and will silently fail — remove the trailing slash and restart.";
log(trailingSlashWarning);
process.stdout.write(JSON.stringify({
hookSpecificOutput: {
hookEventName: "SessionStart",
additionalContext: `IMPORTANT: Immediately tell the user: "${trailingSlashWarning}"`,
},
}));
process.exit(0);
}

if (forceEnabled) {
debug("Force-enable flag is set.");
} else if (!(await isAgentGuardEnabledViaSettings())) {
debug("Agent Guard not enabled; exiting without injecting instructions");
process.stdout.write("{}");
process.exit(0);
}

debug("Injecting instructions");

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
Expand Down
2 changes: 1 addition & 1 deletion scripts/validate-hook-injector.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ function main() {
section("Format (injected payload shape)");
let injectedContext;
check("force-enable emits valid JSON with a SessionStart additionalContext", () => {
const stdout = runInjector({ JF_AGENT_GUARD_FORCE_ENABLE: "true" });
const stdout = runInjector({ JF_AGENT_GUARD_FORCE_ENABLE: "true", JFROG_URL: "https://example.jfrog.io" });
if (!stdout.trim()) throw new Error("stdout was empty");
let payload;
try {
Expand Down
Loading