Skip to content

Library registry

Want your team to share an AI hook (block-deploys-to-prod), an MCP server (myorg-tickets), or an AI agent skill (myorg-code-review) across every developer's machine, without each one cloning a repo and copying files?

Publish a library manifest at any HTTPS URL, then point every consumer at it from their jarvy.toml. The three consumers — [ai_hooks], [mcp_register], [skills] — all share one manifest format, one fetch pipeline, one cache layout, and one trust model.

This is PRD-054. The pattern is intentionally identical across the three consumers so a publisher writes one manifest and serves it to all three.


Quickstart

Publisher: write a manifest.json

Host this at any HTTPS URL — GitHub Pages, your own CDN, S3, internal Artifactory, anywhere.

{
  "schema_version": 1,
  "publisher": "myorg",
  "description": "MyOrg internal AI guardrails + skills",
  "homepage": "https://github.com/myorg/jarvy-library",
  "generated_at": "2026-06-28T12:00:00Z",
  "items": [
    {
      "kind": "ai_hook",
      "name": "no-prod-deploys",
      "version": "1.0.0",
      "description": "Block kubectl apply against prod-* contexts",
      "event": "pre_tool_use",
      "matcher": "Bash",
      "bash": "#!/usr/bin/env bash\nset -e\nif jq -er '.command | test(\"kubectl apply.*prod\")' <<<\"$JSON\"; then\n  echo 'blocked: prod deploys require manual approval' >&2\n  exit 2\nfi",
      "powershell": "if ($json.command -match 'kubectl apply.*prod') { Write-Error 'blocked'; exit 2 }",
      "timeout_ms": 5000
    },
    {
      "kind": "mcp_server",
      "name": "myorg-tickets",
      "version": "0.3.0",
      "description": "Read Linear tickets",
      "command": "myorg-mcp-tickets",
      "args": ["serve"],
      "env": { "LINEAR_API_KEY": "${LINEAR_API_KEY}" },
      "supported_agents": ["claude-code", "cursor"]
    },
    {
      "kind": "skill",
      "name": "myorg-code-review",
      "version": "2.1.0",
      "description": "MyOrg-specific code review checklist",
      "skill_md_url": "https://cdn.myorg.com/jarvy/skills/code-review-2.1.0/SKILL.md",
      "skill_md_sha256": "abc123...",
      "supported_agents": ["claude-code", "cursor", "codex"]
    }
  ]
}

That's the whole spec. Any HTTPS URL serving JSON in this shape is a library.

Consumer: point your jarvy.toml at the URL

Three URL forms are recognized today:

Form Use when Example
https://... Publisher hosts a manifest.json (full PRD-054 surface — ai_hook / mcp_server / skill items) https://cdn.myorg.com/jarvy/manifest.json
git+https://...@<ref> Skills-only; publisher has a Git repo of SKILL.md files (PRD-055) git+https://github.com/myorg/[email protected]#skills/
github:org/repo@<ref> GitHub shorthand for the git form github:anthropics/[email protected]

Git sources are skills-only — AI hooks and MCP server entries still need a manifest because their wire format isn't self-describing. See git sources below for the full surface.

[ai_hooks]
agents = ["claude-code", "cursor"]

[[ai_hooks.library_sources]]
url = "https://cdn.myorg.com/jarvy/manifest.json"

[[ai_hooks.hook]]
use = "no-prod-deploys"           # resolves from library_sources

[mcp_register]
agents = ["claude-code"]
allow_custom_servers = true       # required to enable library servers

[[mcp_register.library_sources]]
url = "https://cdn.myorg.com/jarvy/manifest.json"

[[mcp_register.server]]
use = "myorg-tickets"

[skills]
agents = ["claude-code", "cursor"]

[[skills.library_sources]]
url = "https://cdn.myorg.com/jarvy/manifest.json"

[skills.install]
myorg-code-review = "2.1.0"

Run jarvy setup (or any per-consumer apply command), and Jarvy fetches the manifest, sha-verifies any off-manifest content, and applies the items. Re-running is idempotent.


Manifest format

Field Required Description
schema_version yes 1 today. Bumped only on breaking changes.
publisher yes Short identifier. Used in cache path + telemetry.
description no Human-readable. Surfaced by jarvy library show.
homepage no URL for "where to file bugs". Informational.
generated_at no ISO-8601. Informational.
items yes Array of typed items (see below).

Each item carries a kind discriminator. Today: ai_hook, mcp_server, skill.

ai_hook item

{
  "kind": "ai_hook",
  "name": "no-prod-deploys",
  "version": "1.0.0",
  "description": "Block prod deploys",
  "event": "pre_tool_use",
  "matcher": "Bash",
  "bash": "...inline script body...",
  "powershell": "...optional Windows variant...",
  "timeout_ms": 5000
}

Either bash (inline body) or bash_url + bash_sha256 (off-manifest body fetched over bounded HTTPS and verified against the sha pin before use). Inline bash wins when both are present. A bash_url without bash_sha256 is refused outright — there is no unverified fetch path. powershell_url + powershell_sha256 work the same way for the Windows variant, except fetch failures there are advisory (Jarvy falls back to the warned Windows stub instead of failing provisioning on macOS/Linux).

Fetched bodies land in a content-addressed cache (~/.jarvy/library.d/companions/<sha256>) — because the manifest pins the content hash, repeat resolves are served offline and two libraries referencing the same artifact share one cache entry.

event is one of: pre_tool_use, post_tool_use, user_prompt_submit, session_start, stop, pre_compact, pre_shell_execution.

mcp_server item

{
  "kind": "mcp_server",
  "name": "myorg-tickets",
  "version": "0.3.0",
  "description": "...",
  "command": "myorg-mcp-tickets",
  "args": ["serve"],
  "env": { "LINEAR_API_KEY": "${LINEAR_API_KEY}" },
  "supported_agents": ["claude-code", "cursor"]
}

supported_agents is informational — Jarvy registers with whatever agents the consumer's agents = [...] list says, regardless. The field is surfaced as a warning when there's a mismatch.

skill item

{
  "kind": "skill",
  "name": "myorg-code-review",
  "version": "2.1.0",
  "description": "...",
  "skill_md_url": "https://cdn.myorg.com/jarvy/skills/code-review-2.1.0/SKILL.md",
  "skill_md_sha256": "abc123...",
  "companion_files": [
    { "filename": "helper.sh", "url": "https://cdn.myorg.com/jarvy/skills/code-review-2.1.0/helper.sh", "sha256": "def456..." },
    { "filename": "rules/checklist.md", "url": "https://cdn.myorg.com/jarvy/skills/code-review-2.1.0/rules/checklist.md", "sha256": "789abc..." }
  ],
  "supported_agents": ["claude-code", "cursor"]
}

skill_md_sha256 is required and enforced. Jarvy refuses to install when the fetched body's sha256 doesn't match the manifest entry. A publisher MUST cut a new version + manifest entry when content changes; mutating a versioned artifact in place will surface a clear library.sha_mismatch event.

companion_files (optional) lists extra files installed alongside SKILL.md — templates, helper scripts, rule files. Every entry carries a mandatory sha256 pin, enforced the same way. filename is a forward-slash relative path inside the skill directory; subdirectories are allowed (rules/checklist.md), but absolute paths, ../. components, backslashes, drive-letter colons, control bytes, and the jarvy-owned names SKILL.md / .jarvy-skill.json are refused (library.companion.refused_filename). Companions are fetched + verified before anything is written, so a tampered or hostile entry never leaves a half-installed skill directory. The .jarvy-skill.json sidecar records each companion's filename + sha for drift detection.


Trust model

Config origin library_sources allowed?
Local (your own jarvy.toml or ~/.jarvy/config.toml) Yes
Remote (jarvy setup --from <url>) No — refused with library.remote_refused event

Mirrors [packages] allow_remote and [ai_hooks] allow_custom_commands semantics. A remote-fetched config may NARROW trust (drop a library_source you'd otherwise pull) but never BROADEN it (add a library_source you haven't approved). There is no override flag — adding one would defeat the entire purpose.

Teams that want to ship library_sources to every developer copy them into each developer's local ~/.jarvy/config.toml instead.


Signature verification

The config schema supports cosign + a sha256 manifest pin:

[[ai_hooks.library_sources]]
url = "https://cdn.myorg.com/jarvy/manifest.json"
require_signature = true                              # default
identity_regexp = "^https://github\\.com/myorg/jarvy-library/.+$"
oidc_issuer = "https://token.actions.githubusercontent.com"
manifest_sha256 = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15ac1e289f66085"

Cosign verification is scaffolded but not enforced in v1. The fields parse and round-trip; require_signature = false emits a library.signature_disabled event today, and require_signature = true (the default) emits a library.signature_unenforced warning every fetch so operators can't silently believe Sigstore protects them yet. Enforcement lands in a follow-up phase, gated on the same cosign integration used by jarvy registry sync.

manifest_sha256 is the strongest tamper-evidence available today. When set, every sync recomputes the sha over the raw bytes the publisher served and refuses to apply if it doesn't match — even a publisher who silently re-publishes under the same URL cannot land new content without a visible config bump. On mismatch, library.sync.failed fires with error_kind = "manifest_sha_mismatch" and the cached copy is preserved.

For production use today, pin manifest_sha256 and bump it deliberately when you update the published manifest. Treat URLs without a sha pin like any other unsigned HTTPS dependency: audit the publisher and assume they could ship malicious content.


Cache

Manifests cache to disk at:

~/.jarvy/library.d/<sha256-of-url>/manifest.json

Companion artifacts (hook bash_url / powershell_url bodies, skill companion_files) cache content-addressed at:

~/.jarvy/library.d/companions/<sha256-of-content>

Because the manifest pins the content hash, a companion cache hit is immutable-by-construction: it's verified by re-hashing on read, corrupted entries fall through to a re-fetch, and repeat installs are served offline.

The URL hash is collision-free; the directory layout is internal and may change. Use jarvy library list (when shipped) or read it directly with jq.

Refetch happens on every apply / install call unless the on-disk copy is fresher than refresh_interval_secs (default 86400 = 24h). On network failure, the cached copy is served with a library.fetch.cached_hit reason="fetch_failed" event so you can tell from logs that you're running stale.


Telemetry

All events route through the existing OTEL pipeline. Stable contract:

Event When Key fields
library.sync.started fetch begins url, require_signature
library.sync.completed fetch + parse OK url, items_synced, ai_hook_count, mcp_server_count, skill_count, from_cache, signature_verified
library.sync.failed every sync error path url, scheme = "manifest" \| "git", error_kind, error
library.fetch.cached_hit served from cache url, reason
library.cache.write_failed disk-write best-effort failure url, error
library.signature_disabled require_signature = false url
library.signature_unenforced require_signature = true (cosign not yet enforced in v1) url, advice
library.remote_refused trust-gate refusal consumer
library.companion.fetched companion artifact fetched + sha-verified url, bytes, from_cache (debug level on cache hits)
library.companion.sha_mismatch fetched body doesn't match the manifest pin url, expected, actual
library.companion.fetch_failed any other companion fetch error url, error_kind, error
library.companion.refused_filename skill companion filename failed path-safety validation skill, reason (filename itself NOT logged — attacker-controllable)
skills.installed per-skill install skill, version, agent_count, skipped_count

The full envelope (including the git scheme events under library.git.*) lives in CLAUDE.md::Telemetry/Event Taxonomy.


Bounds + safety

  • HTTPS-only. Non-HTTPS URLs refused at the fetch boundary. (Loopback HTTP is allowed only when the binary was built with the test-bypass Cargo feature AND JARVY_LIBRARY_ALLOW_INSECURE_FETCH=1 is set. Release builds ship without the feature so the env var is inert.)
  • Manifest cap: 16 MiB. Per-companion-artifact cap: 1 MiB. Larger needs override or split into multiple libraries.
  • Userinfo bypass refused: http://127.0.0.1:80@attacker/x is parsed as authority + userinfo and rejected.
  • Process cache survives the run; disk cache survives across runs. Both are wiped by jarvy library clean (when shipped).

Comparison with jarvy registry sync

Both fetch HTTPS-hosted JSON, sha-verify content, and cache locally. Differences:

Tools registry Library registry
Configures ~/.jarvy/config.toml's [registry] (single source) Per-consumer library_sources = [...] in jarvy.toml (multiple sources)
Trust gate Project configs can't subscribe to a registry Remote configs can't declare library_sources
Cosign Enforced today Scaffolded; enforcement in follow-up
Items Tool definitions (TOML) AI hooks / MCP servers / skills (JSON, tagged)
Apply jarvy registry sync (explicit) Implicit on jarvy setup / consumer apply

The two will likely converge on a shared core in a future Jarvy release. For now they're parallel.


Git sources (PRD-055)

Skills can also come from a plain Git repo — no manifest.json required. Jarvy clones the repo at the pinned ref, walks the optional subpath for SKILL.md files, parses each file's YAML frontmatter, and synthesizes a manifest in-memory.

[[skills.library_sources]]
url = "git+https://github.com/myorg/[email protected]#skills/"

# Or shorthand:
[[skills.library_sources]]
url = "github:anthropics/[email protected]"

URL grammar

git+https://<host>/<owner>/<repo>.git@<ref>[#<subpath>]
github:<owner>/<repo>@<ref>[#<subpath>]
Component Required Notes
@<ref> yes Tag, branch, or commit SHA. Unpinned URLs (no @) are refused at parse time.
#<subpath> no Path inside the repo to scan. Default = repo root. .. and absolute paths refused.

SKILL.md frontmatter

Each SKILL.md under the scanned subpath becomes one skill item. Required frontmatter:

---
name: code-review                    # required — used as skill identifier
version: 2.1.0                       # required — used as the manifest version
description: MyOrg code review checklist
supported_agents:                    # optional; default = all
  - claude-code
  - cursor
---

# Code Review Skill

(body — anything the agent should read)

Files missing name or version are skipped with a library.git_skill.skipped event citing the reason. No silent failures.

Ref pinning + trust

Ref type Trust posture
Commit SHA (@abc1234 or full 40-char) Tamper-evident. Recommended for production.
Tag (@v1.2.0) Mutable (publishers can re-tag). Recommended for ergonomics + version visibility.
Branch (@main) Freely mutable. Emits library.git.mutable_ref warning every fetch. Documented as dev-only.

The mutable-ref warning is advisory — Jarvy does not refuse branches because they're a legitimate dev workflow ("track our internal main skills branch on every laptop"). For the strongest guarantee, pin to a commit SHA.

What you need

  • git CLI on PATH. Missing git refuses with a clear error pointing at [provisioner] git = "latest". No libgit2 dependency.
  • HTTPS-reachable Git host. SSH (git+ssh://) is not supported in v1.

Why not also AI hooks + MCP servers?

SKILL.md carries its own frontmatter — Jarvy has everything needed to build a manifest entry from one file. AI hook script bodies and MCP server command/args/env tables don't have an equivalent self-describing format. For those, publishers still ship a manifest.json at the repo root and use the URL form.

Cache

~/.jarvy/library.d/<sha256-of-url>/
  manifest.json                  # synthesized from SKILL.md frontmatter
  git/
    <cloned repo tree at ref>
      SKILL.md
      skills/
        code-review/
          SKILL.md

The clone is shallow (--depth 1). Re-running jarvy skills install refreshes via git fetch + git checkout <ref>; offline runs fall back to the cached synthesized manifest with a library.git.cache_hit event.


What's next

  • Cosign signature enforcement (PRD-054 phase 5)
  • jarvy library {sync, list, show, clean} subcommand (phase 6)
  • Public reference library (a community-maintained manifest of common hooks)
  • git+ssh:// auth for private repos (PRD-055 follow-up)
  • Sparse-checkout for large repos (PRD-055 follow-up)
  • AI hooks + MCP servers from Git via a manifest.json at repo root (already works — documented above)

Track follow-up under prd/054-library-registry.md and prd/055-git-skill-sources.md.


  • AI hooks — built-in LIBRARY const + library_sources consumer
  • MCP registration — built-in jarvy server + library_sources consumer
  • Skills — PRD-049 install pipeline