Skip to content
Draft
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
7 changes: 7 additions & 0 deletions .github/scripts/sync-modules-vendor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"repo": "jfrog/jfrog-agent-hooks",
"pin": "b680ef382997c57e806e59182bab0ebbfdec87dd",
"paths": [
"modules"
]
}
1 change: 1 addition & 0 deletions .github/workflows/validate-inject-instructions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ on:
- "plugin/scripts/inject-instructions.mjs"
- "plugin/templates/jfrog-mcp-management.md"
- "plugin/hooks/hooks.json"
- "plugin/modules/**"
- "plugin/.claude-plugin/plugin.json"
- "marketplace.json"
- "scripts/validate-hook-injector.mjs"
Expand Down
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The JFrog plugin provides the following capabilities, grouped by component:
| --- | --- | --- |
| **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. |
| **Hook** | Agent Package Resolution (Preview) | Inject Artifactory routing instructions at the start of each Copilot session. |

---

Expand All @@ -27,7 +28,8 @@ Before installing, make sure you have:
- **JFrog host URL and access token** — Your JFrog platform URL and a valid access token.
- **VS Code** — With the **GitHub Copilot Chat** extension installed and signed in.
- **GitHub Copilot editor preview features enabled** (organizations only) — If your Copilot access is managed by a GitHub organization, an admin must navigate to **Settings → Copilot → Policies → Editor preview features** and set it to **Enabled**. Individual (non-org) Copilot users can skip this step.
- **Node.js** (≥ 14) — with `npx` on your `PATH`
- **Agent hooks enabled** — Set `"chat.useHooks": true` in VS Code settings.
- **Node.js** (≥ 20) — available as `node` on your `PATH`
- **JFrog CLI** (≥ 2.x, optional) — Recommended for `jf config add` authentication (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 project** (optional) — If you want to use the Agent Guard feature.
Expand Down Expand Up @@ -103,6 +105,18 @@ If you have never configured the JFrog CLI on this machine:

After authentication, open a workspace in VS Code. The session-start hook installs the governance file, the JFrog Agent Guard becomes active, and any MCP servers approved for your project become available to your Copilot agent. You can manage everything through natural language — no terminal commands required.

### Agent Package Resolution

When Agent Package Resolution is enabled in `~/.jfrog/agents-conf.json`, a
SessionStart hook adds the resolved Artifactory repositories and package-routing
rules to every new Copilot chat. Configure the JFrog CLI with `jf config add`,
then start a new chat after changing the configuration.

The feature is fail-open for the chat session: disabled or unexpected failure
returns an empty hook result instead of preventing Copilot from starting. An
enabled but unconfigured installation injects a `NOT READY` advisory with setup
instructions.

### Discover, inspect, and install MCPs

| Ask the agent… | What happens |
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.9",
"version": "1.0.10",
"source": "plugin",
"categories": [
"security",
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.9",
"version": "1.0.10",
"author": { "name": "JFrog", "url": "https://jfrog.com" },
"hooks": "hooks/hooks.json"
}
10 changes: 10 additions & 0 deletions plugin/hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@
"timeout": 7
}
]
},
{
"hooks": [
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/modules/vscode-session-start.mjs\" package-resolution",
"timeout": 7,
"statusMessage": "Routing package installs through JFrog Artifactory…"
}
]
}
]
}
Expand Down
18 changes: 18 additions & 0 deletions plugin/modules/assets/agents-default-conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"logLevel": "info",
"packageResolution": {
"enabled": false,
"verifyRepos": true,
"cacheTtlDays": 7,
"defaultGlobalRepos": {
"npm": "npm-virtual",
"pypi": "pypi-virtual",
"maven": "maven-virtual",
"go": "go-virtual",
"docker": "docker-virtual",
"helm": "helm-virtual",
"nuget": "nuget-virtual"
},
"autoSetup": []
}
}
77 changes: 77 additions & 0 deletions plugin/modules/claude-session-start.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env node
// Claude Code SessionStart hook runner.
//
// Usage: node claude-session-start.mjs <capability>
// Example: node claude-session-start.mjs package-resolution
//
// stdout: JSON with hookSpecificOutput.additionalContext. No stdout is a no-op.

import process from "node:process";

import { runCapability } from "./core/run-capability.mjs";
import { ensureAgentsConfigScaffold, agentsConfigLoadWarnings } from "./core/agents-config.mjs";
import { readStdin, parseSessionId, detectHarness, parseWorkspaceRoots } from "./core/io.mjs";
import { setLogContext, createLogger } from "./core/logger.mjs";

const HARNESS_ID = "claude_code";
const log = createLogger("session-start");

/** @returns {string | null} JSON stdout payload, or null when there is nothing to inject. */
function formatSessionStartStdout(text) {
if (!text?.trim()) return null;
return JSON.stringify({
hookSpecificOutput: {
hookEventName: "SessionStart",
additionalContext: text,
},
});
}

function writeStdout(payload) {
if (payload !== null) process.stdout.write(payload);
}

function writeNoOp() {
// Claude SessionStart: no stdout on no-op.
}

async function main() {
const capability = process.argv[2];
if (!capability) {
writeNoOp();
return;
}

const startedAtMs = Date.now();
const stdinRaw = await readStdin();
const harness = detectHarness(stdinRaw);
if (harness && harness !== HARNESS_ID) {
setLogContext({ ide: HARNESS_ID, sessionId: parseSessionId(stdinRaw) });
log.warn("harness mismatch; wrong adapter invoked", {
expected: HARNESS_ID,
detected: harness,
adapter: "claude-session-start",
});
writeNoOp();
return;
}
const sessionId = parseSessionId(stdinRaw);
const workspaceRoots = parseWorkspaceRoots(stdinRaw);
setLogContext({ ide: HARNESS_ID, sessionId });
ensureAgentsConfigScaffold();
for (const w of agentsConfigLoadWarnings()) {
log.warn(w.message, { path: w.path });
}
const text = await runCapability(capability, {
ide: HARNESS_ID,
sessionId,
workspaceRoots,
startedAtMs,
});
writeStdout(formatSessionStartStdout(text));
}

main().catch(() => {
writeNoOp();
process.exit(0);
});
Loading
Loading