Audience: Authors who want to produce conformant MDA output without the
@mda/clireference implementation. This document covers two of the three authoring modes fromspec/v1.0/00-overview.md §0.6: Human mode (you, with a text editor) and Agent mode (an AI agent, with this document loaded into its context).Compiled mode (writing
.mdaand running the compiler) is documented inpackages/mda/IMPL-SPEC.md.
This document is the bootstrap path for a v1.0 ecosystem in which most authors do NOT have the reference compiler installed, and many authors are LLMs working from prompts. It is intentionally self-contained: every step here uses standard Unix tools (sha256sum, cosign) plus a JSON Canonicalization Scheme (JCS) helper.
You are writing one of:
| Target | Filename | When |
|---|---|---|
| SKILL.md | <name>/SKILL.md (+ scripts/, references/, assets/ as needed) |
You are publishing a skill that consumers (Claude Code, Codex, OpenCode, Hermes, Cursor, Windsurf, …) will activate on demand. |
| AGENTS.md | AGENTS.md at the repo root |
You are giving repo-wide instructions to every agent that visits the repository (Codex CLI, Copilot, Cursor, Windsurf, Amp, Devin, Gemini CLI, VS Code, Jules, Factory). |
| MCP-SERVER.md | <name>/MCP-SERVER.md + <name>/mcp-server.json |
You are describing an MCP server: what tools it exposes, how to launch it, what trust posture it ships with. |
| CLAUDE.md | CLAUDE.md |
You are populating Claude Code's persistent project-memory file. v1.0 stub only; no schema enforcement. |
Pick exactly one per file. The schema you target is determined by the filename, not by the content.
Open with --- on the first line, then your YAML, then --- on its own line.
---
name: pdf-tools # kebab-case, 1-64 chars
description: Extract PDF text, fill forms, merge files. Use when handling PDFs. # 1-1024 chars
---# Agent instructions
Use 2-space indentation. Run `pnpm test` before pushing.That is a complete, conformant AGENTS.md.
In any compiled output (any .md file), the following fields MUST nest under metadata.mda.* — never at the top level:
doc-id, title, version, requires, depends-on, tags, author, relationships, created-date, updated-date.
Example:
---
name: pdf-tools
description: Extract PDF text, fill forms, merge files. Use when handling PDFs.
metadata:
mda:
doc-id: 38f5a922-81b2-4f1a-8d8c-3a5be4ea7511
title: PDF Tools
version: "1.2.0" # quoted!
tags: [pdf, extraction]
requires:
runtime: ["python>=3.11"]
tools: ["Read", "Bash"] # bare names are portable; vendor-specific
# syntax (e.g. Bash(pdftotext:*)) belongs
# under metadata.<vendor>.* — see §2.4
network: none
created-date: "2026-01-15T00:00:00Z" # quoted!
---metadata:
mda: { ... }
claude-code:
allowed-tools: "Read Bash(pdftotext:*) Bash(jq:*)"
codex:
display_name: "PDF Tools"
allow_implicit_invocation: trueThe MDA spec does not interpret per-vendor namespaces. Whatever the vendor's loader expects, you write here.
YAML 1.1 parsers will silently coerce created-date: 2026-05-07T00:00:00Z into a native datetime. The schema requires a string. Always quote:
created-date: "2026-05-07T00:00:00Z"
version: "1.2.0"Standard Markdown. The body has no frontmatter.
- For SKILL.md: keep it focused. Heavy reference material belongs in
references/. Executable code belongs inscripts/. Templates and fixtures belong inassets/. - For AGENTS.md: cover coding conventions, build/test instructions, and operational notes.
- For MCP-SERVER.md: describe each tool and resource the server exposes, the input/output shape, side effects, and required environment variables.
When you want to declare a typed link to another document, use a standard Markdown footnote whose payload is a JSON object:
This skill builds on the parent doc[^p].
[^p]: {"rel-type": "parent", "doc-id": "11111111-1111-1111-1111-111111111111", "rel-desc": "Conceptual parent"}Valid rel-type values: parent, child, related, cites, supports, contradicts, extends.
When you use one or more relationship footnotes, you MUST also mirror them under metadata.mda.relationships in the frontmatter:
metadata:
mda:
relationships:
- rel-type: parent
doc-id: 11111111-1111-1111-1111-111111111111
rel-desc: Conceptual parentThe order of mirror entries MUST match the order in which the footnote references first appear in the body.
When you want a content hash anchor for tamper detection — and always when you intend to sign the file — compute a SHA-256 digest over the canonical bytes per spec/v1.0/08-integrity.md and add:
integrity:
algorithm: sha256
digest: "sha256:<64 lowercase hex chars>"The digest value carries an <algorithm>: prefix that MUST match the algorithm field. This is the same shape used by metadata.mda.depends-on[].digest so consumers can compare them byte-for-byte.
The reference CLI exposes mda canonicalize for this. Without the CLI:
- Strip
integrityandsignaturesfrom the frontmatter (set them aside). - Convert the (stripped) frontmatter to JSON and run it through a JCS (RFC 8785) library:
- JS:
@truestamp/canonify - Python:
jcs(PyPI) - Go:
gowebpki/jcs
- JS:
- Normalize the body: convert
\r\nto\n, strip trailing whitespace per line, ensure exactly one final\n. - Concatenate:
b"---\n" + JCS_OUT + b"\n---\n" + NORMALIZED_BODY. - Hash with
sha256sum(or your language's stdlib).
When the target is MCP-SERVER.md, the integrity digest covers BOTH the Markdown file AND the mcp-server.json sidecar, in that order, separated by b"\n--MDA-FILE-BOUNDARY--\n". See spec/v1.0/08-integrity.md §08-3.4.
The default signing path is Sigstore OIDC keyless via cosign. It does not require you to manage a long-lived signing key.
PAE = "DSSEv1" + " " + len(payload-type) + " " + payload-type
+ " " + len(payload-bytes) + " " + payload-bytes
For MDA:
payload-type=application/vnd.mda.integrity+jsonpayload-bytes= JCS-canonicalized{"algorithm":"sha256","digest":"sha256:<your-hex>"}— thedigestvalue MUST be the prefixed form, byte-for-byte identical to what you put under top-levelintegrity.digest.
echo -n "$PAE_BYTES" | cosign sign-blob - --output-signature sig.b64 --output-certificate cert.pemCosign opens an OIDC browser flow, mints a short-lived Fulcio cert, and submits the entry to Rekor.
signatures:
- signer: "sigstore-oidc:<oidc-issuer-from-fulcio-cert>"
key-id: "fulcio:<sha256-of-cert.pem>"
payload-digest: "<same as integrity.digest>"
algorithm: ecdsa-p256
signature: "<contents of sig.b64>"
rekor-log-id: "<from cosign output>"
rekor-log-index: <from cosign output>If you cannot reach Sigstore, host a mda-keys.json document at https://<your-domain>/.well-known/mda-keys.json (schema: schemas/_defs/mda-keys.schema.json), sign the PAE bytes with the corresponding private key, and emit:
signatures:
- signer: "did-web:<your-domain>"
key-id: "<fingerprint matching a key in mda-keys.json>"
payload-digest: "<same as integrity.digest>"
algorithm: ed25519
signature: "<base64>"did:web does NOT provide transparency-log inclusion guarantees. Use it only when Sigstore is unavailable.
You have three options, in increasing order of strictness.
If your file matches the examples in this document and the targeted spec section, you are probably fine.
Extract the frontmatter, convert to JSON, validate against:
schemas/frontmatter-skill-md.schema.json(for SKILL.md)schemas/frontmatter-agents-md.schema.json(for AGENTS.md)schemas/frontmatter-mcp-server-md.schema.json(for MCP-SERVER.md)
Any standard JSON Schema 2020-12 validator works (Ajv, jsonschema-py, gojsonschema, …).
node scripts/validate-conformance.mjsThis is what CI runs against the in-tree fixtures. To validate YOUR file, point a JSON Schema 2020-12 validator at it as in §6.2 above; the runner does not accept arbitrary user files.
- Bare timestamps.
created-date: 2026-05-07T00:00:00Z(no quotes) gets coerced to a native date and breaks validation. Always quote. - Top-level MDA-extended fields.
doc-idand friends MUST nest undermetadata.mda.*in compiled outputs. The strict schema rejects top-level placement. allowed-toolsat the top of AGENTS.md. AGENTS.md does not define this field at the top level; place it under the relevant vendor namespace (e.g.metadata.claude-code.allowed-tools).- Footnotes without mirror. If you use relationship footnotes in the body, the
metadata.mda.relationshipsmirror is REQUIRED. The mirror order MUST match the footnote-reference order. signatures[]withoutintegrity. The schema rejects this combination. Compute and includeintegritywhenever you sign.- Hashing without canonicalization. A digest computed over raw YAML bytes will not match what verifiers reproduce. Always canonicalize via JCS first.
- Compound version-range.
>=1.2.0 <2.0.0is NOT in the v1.0 admitted subset. Use1.2.0(exact) or^1.2.0(caret) only.
This section is a prompt template intended for an LLM that has been asked to produce a conformant MDA file. The agent SHOULD have THIS document and the relevant target spec section in its context.
You are about to write an MDA
<TARGET>.mdfile for the<NAME>artifact.
- Decide which fields are required and which are optional for this target (see §2 above).
- Place every MDA-extended field under
metadata.mda.*. Place every per-vendor field undermetadata.<vendor>.*.- Quote every date and the
versionfield as YAML strings.- If you are writing relationship footnotes in the body, also write the
metadata.mda.relationshipsmirror in the same order.- If the user asked for
integrity, either (a) compute the canonical digest yourself per §4.1 above and emit the final<algorithm>:<hex>value, or (b) emit the file WITHOUT anintegrityfield and tell the human "I cannot compute the canonical digest from this turn; runmda canonicalize | sha256sum(or follow §4.1) and paste the result back." Never emit a placeholder digest — schema-valid placeholders falsely advertise tamper protection.- If the user asked for
signatures, instruct the human to follow §5 above; do NOT fabricate signatures.- After writing, mentally validate against the target schema bullet-points in §6.2 and report any field you are unsure about.
Do not invent fields. The strict schemas reject unknown top-level fields.