■ Protocoles d’agents ouverts

Spécification de confiance Forge

Version 0.1Juin 2026Brouillon · RFC ouverte

Le bloc d’extension forge est un objet JSON que vous ajoutez à n’importe quel manifeste de serveur MCP, Agent Card A2A ou frontmatter SKILL.md. Il porte des signaux de confiance — identité vérifiée de l’éditeur, manifestes signés, résultats de l’analyse de sécurité et état de révocation. Il ne modifie pas les standards sous-jacents et les clients qui ne le lisent pas l’ignorent.

01Vérification de l’éditeur
02Manifestes signés
03Analyse de sécurité
04Révocation
05Évaluations · prévu

Ce document n’est maintenu qu’en anglais. La version anglaise fait foi.

Version: 0.1
Status: Draft · Open RFC


Trust model — read this first

The forge block is a claim, not a proof. Anyone can write "verified": true into their own manifest. A manifest is controlled by its author; nothing in the block itself prevents forgery.

Consumers (CLIs, registries, AI clients) MUST treat the block as a cache hint and verify trust through one of two channels:

  1. Registry lookup — query GET https://forgeregistry.com/api/v1/packages/:id and compare. The registry's verification records are the source of truth.
  2. Signature verification — verify the publisher's Ed25519 signature over the canonical payload (see Signing below) against the public key served by the registry.

A consumer that renders a "verified" badge based solely on the block's contents is non-conformant with this spec.


Signing and key distribution

When a publisher runs forge publish, the CLI generates an Ed25519 keypair (stored locally in ~/.forge/keys/) and signs the canonical payload:

forge:v1:{package_name}:{github_url}:{github_login}

The public key (SPKI PEM) and the base64 signature are stored in the registry's verification record and served from GET /api/v1/packages/:id as verified_publisher.publicKey and verified_publisher.signature.

Key distribution: consumers fetch the public key from the registry at verification time. This is trust-on-first-use anchored at the registry — the registry binding key → GitHub identity is the root of trust. A transparency-log model (Sigstore) is under evaluation for v0.2 to remove the registry as a single point of trust; see the roadmap.

Key rotation: re-running forge publish with a new keypair replaces the stored key and signature. Old signatures become invalid immediately.


Revocation

Verification can be withdrawn — e.g. a maintainer account is compromised or a malicious update ships. Revoked entries keep their record (audit trail) but gain a revoked block in the registry's API response:

"revoked": {
  "revokedAt": "2026-06-11T09:00:00Z",
  "reason": "maintainer account compromised"
}

Consumers MUST check for revoked on every verification and treat a revoked package as untrusted regardless of any forge block contents, signatures, or cached state. forge verify exits non-zero for revoked packages.


Top-level structure

The forge block is added as a top-level key in your existing manifest file.

manifest root
└── forge                  object, optional
    ├── version            string, required if forge block is present
    ├── publisher          object, optional — identity & verification
    ├── security           object, optional — scan results
    ├── credentials        array,  optional — what the user must supply to run it
    ├── evals              object, optional — eval suites you invite people to run
    └── compose            object, optional — composability hints       [planned]

forge.version

Field Type Required
version string yes (if forge block present)

The version of this spec the block conforms to. Must be "0.1" for this version of the spec.

"forge": {
  "version": "0.1"
}

forge.publisher

Populated automatically by Forge when a listing is claimed. You can include it manually if you prefer — but remember it is a claim: consumers verify it against the registry's records or the publisher's signature (see Trust model above), never at face value.

Field Type Description
verified boolean true if Forge has verified publisher identity
verification_method string How verification was performed (see values below)
github_login string GitHub username of the verified publisher
verified_at string ISO 8601 datetime when verification occurred

verification_method values:

Value Description
github-owner GitHub login matches the repo owner
github-collaborator-api Forge confirmed repo access via GitHub collaborator API
manual Manually reviewed and approved by the Forge team
"forge": {
  "version": "0.1",
  "publisher": {
    "verified": true,
    "verification_method": "github-collaborator-api",
    "github_login": "sam-rivera",
    "verified_at": "2026-05-27T14:30:00Z"
  }
}

forge.security

Populated automatically by Forge after a security scan runs. Read-only from the publisher's perspective — Forge writes these values; they are not self-reported.

Field Type Description
scanned boolean true if a scan has been run
scan_level string "base" (default) or "verified"
last_scan string ISO 8601 datetime of most recent scan
scanner_version string Version of the Forge scanner that ran
vulnerabilities integer Total known CVEs for this version (from OSV database)
critical integer Critical severity vulnerabilities
high integer High severity vulnerabilities
moderate integer Moderate severity vulnerabilities
low integer Low severity vulnerabilities
suspicious_scripts boolean true if lifecycle scripts contain flagged patterns
status string "clean" | "warnings" | "critical" | "failed"
"forge": {
  "version": "0.1",
  "security": {
    "scanned": true,
    "scan_level": "base",
    "last_scan": "2026-05-27T14:30:00Z",
    "scanner_version": "0.1.0",
    "vulnerabilities": 0,
    "critical": 0,
    "high": 0,
    "moderate": 0,
    "low": 0,
    "suspicious_scripts": false,
    "status": "clean"
  }
}

forge.credentials

What a user must have in hand before your package will run. Declared by you; displayed by Forge on the listing and scaffolded into the generated client config as <YOUR_NAME> placeholders.

Forge stores credential names, never credential values. It does not hold tokens, broker OAuth, or sit in the request path at runtime — this field is metadata that helps a user get their credentials into their client. Do not put an actual key in description: descriptions that look like they contain one are dropped on ingest.

Field Type Description
name string Environment variable name, ^[A-Z][A-Z0-9_]{1,63}$
label string Human-readable label; derived from name when omitted
kind string "oauth" | "api-key" | "env-var"; inferred from name when omitted
required boolean Cannot run without it. Default false
description string What it is for (≤200 chars)
obtain_url string https URL where the user creates or retrieves the value
"forge": {
  "version": "0.1",
  "credentials": [
    {
      "name": "BRAVE_API_KEY",
      "label": "Brave Search API Key",
      "kind": "api-key",
      "required": true,
      "description": "Used for every search call. The free tier is enough for personal use.",
      "obtain_url": "https://brave.com/search/api/"
    }
  ]
}

Where a declaration comes from decides how Forge presents it, in descending order of authority:

  1. Publisher-declared — this block, submitted by a publisher who passed the repo-ownership check. Shown as declared by the verified publisher.
  2. Registry-declaredpackages[].environmentVariables in the official MCP registry, mirrored by the indexer.
  3. Inferred — read from the "env" object in your README's own client-config snippet, and labelled as such. Nothing inferred is ever marked required.

A declared list replaces the ones below it rather than adding to them. A declaration is a complete statement, so if you declare two credentials, Forge shows exactly those two — declaring the block is how you correct anything it guessed wrong.


forge.evals

The eval suites you invite people to run against your package.

This block carries no results, and that is the whole design. An earlier draft of this spec had accuracy, latency_p50_ms and an attestation field whose values included "forge-verified". Every one of those is a string in a file you control: "accuracy": 0.99 costs a publisher nothing to write, and a consumer nothing to disbelieve. A manifest cannot carry evidence, so this one carries the commitment that makes evidence possible — which suite, at which version, with which bytes, fetchable from where.

Results live in the registry as attributable runs, submitted by forge eval --publish and stamped with the GitHub identity the registry authenticated. A result is labelled reproduced only when two independent accounts ran the same suite digest against the same version of your package and agreed; a single run — yours included — is labelled an unverified claim and displayed as one. See GET /api/v1/entries/:id/evals.

Field Type Description
suites array Suites this package should be evaluated against (max 8)

Each entry of suites:

Field Type Description
id string Suite id, ^[a-z0-9][a-z0-9._/-]{2,63}$, e.g. forge/mcp-smoke-v1
version string Suite version. Bump it whenever a case changes
digest string sha256:<64 hex> over the suite's canonical content
url string https URL where the suite JSON can be fetched
"forge": {
  "version": "0.1",
  "evals": {
    "suites": [
      {
        "id": "forge/mcp-smoke-v1",
        "version": "1.0.0",
        "digest": "sha256:4f1c…",
        "url": "https://raw.githubusercontent.com/acme/server/main/evals/smoke.json"
      }
    ]
  }
}

What the digest covers. The suite's id, version, kind, and cases, canonicalised (object keys sorted, no whitespace) and hashed with SHA-256. description and url are excluded: they are prose and a hosting detail, and a mirror of the same suite at a different URL must still be able to corroborate the original. Anything that changes what is measured is inside the digest. Compute it with forge eval (it prints the digest of any suite it runs), or see eval-suite.schema.json for the suite format.

Why deterministic assertions only. A suite case asserts substrings, regular expressions, or error-or-not over an MCP tool response. There is no model-judged tier, because a judged result stops being reproducible the moment the judge model is deprecated or its sampling settings differ — the reporter's number would then differ from yours for reasons that have nothing to do with your package, and the registry could never tell that apart from a real disagreement.

What Forge stores, and what it derives

Stored per run suite reference (id, version, digest, url, case count), the exact version evaluated, passed/total, latency p50/p99, a transcript digest, the runner, when it ran, and the authenticated reporter
Derived, never submitted credibility (unverified / reproduced / contested), the reported accuracy, the spread across reporters, and whether all reporters got byte-identical per-case outcomes
Never stored any assertion by the reporter about how trustworthy their own run is

A run is rejected if its case count differs from the suite's — a partial run is not a result, because the cheapest route to a perfect score would otherwise be to run only the cases you pass.

Eval results never affect the trust score. The trust score is a security rubric (identity + scan findings) under an explainability commitment; accuracy is a different question, and it is authored by the party being measured. Mixing them would make the one number the registry's credibility rests on mean neither thing. Quality ships as a parallel signal with its own badge and its own --min-accuracy filter — which only reproduced results satisfy.


forge.compose — planned

Not yet implemented. Specified here for early adopter feedback.

Field Type Description
depends_on string[] Package IDs this package calls at runtime
can_be_called_by string[] Package IDs that may invoke this package

Complete example

{
  "name": "web-researcher",
  "description": "Deep web research agent",
  "url": "https://example.dev/agents/web-researcher",
  "version": "1.4.2",

  "forge": {
    "version": "0.1",

    "publisher": {
      "verified": true,
      "verification_method": "github-collaborator-api",
      "github_login": "ana",
      "verified_at": "2026-05-27T14:30:00Z"
    },

    "security": {
      "scanned": true,
      "scan_level": "base",
      "last_scan": "2026-05-27T14:30:00Z",
      "scanner_version": "0.1.0",
      "vulnerabilities": 0,
      "critical": 0,
      "high": 0,
      "moderate": 0,
      "low": 0,
      "suspicious_scripts": false,
      "status": "clean"
    }
  }
}

Usage by manifest format

MCP server (package.json)

Add the forge block as a top-level key in package.json:

{
  "name": "my-mcp-server",
  "version": "1.0.0",
  "forge": { "version": "0.1", "publisher": { ... } }
}

A2A Agent Card (agent.json)

Add the forge block as an extension key in the Agent Card:

{
  "name": "My Agent",
  "url": "https://example.com/agent",
  "version": "1.0",
  "forge": { "version": "0.1", "publisher": { ... } }
}

SKILL.md frontmatter

Add a forge key in the YAML frontmatter:

---
name: code-reviewer
version: 1.0.0
forge:
  version: "0.1"
  publisher:
    verified: true
    github_login: sam-rivera
    verified_at: "2026-05-27T14:30:00Z"
---

Changelog

v0.1 — 2026-05-27

Initial draft. Defines forge.version, forge.publisher, and forge.security. Specifies forge.evals and forge.compose as planned fields.

2026-08-20 — forge.evals (replaces the planned draft)

Turns forge.evals from a planned results block into a declaration of the eval suites a publisher invites people to run: suites[].{id, version, digest, url}. The previously specified result fields (accuracy, latency_p50_ms, total_runs, last_evaluated) and the attestation enum are removed — they were self-reported values in a publisher-controlled file, and attestation: "forge-verified" in particular was a claim of Forge's own endorsement that Forge had never made. Results now live in the registry as runs attributed to an authenticated identity, and credibility is derived from them rather than declared. Backwards compatible in the direction that matters: nothing was implemented against the old shape, and the field remains optional.

2026-08-20 — forge.credentials

Adds forge.credentials: the credential names a package needs, their kind (oauth / api-key / env-var), whether each is required, and where to obtain it. Declarative only — Forge stores no credential values, brokers no tokens, and remains outside the runtime path. Backwards compatible: the field is optional, and its absence means "not declared", not "needs nothing".

2026-06-11 — clarifications (no schema change)