agentleFS
NizokaNizoka/pkinative-mcp/llms.txt

pkinative-mcp

llms.txt0 starsChanged 20 days agoMITView on GitHub

MCP server for PKI — 34 tools on the pkinative engine (zero-dependency, pure TypeScript, Web Crypto for every signature; OpenSSL 3 interoperability validated in CI), for Claude Desktop, Cursor, ChatGPT and any Model Context Protocol client. In detail: inspecting and verifying X.509 certificates, PKCS#10 requests, CRLs, OCSP responses, CMS SignedData and RFC 3161 time-stamps; building and validating certificate chains (RFC 5280 §6 path validation, RFC 6125 name checks, extended key usage, policies, offline CRL and OCSP revocation); creating certificates (self-signed…

  • Reads credentials

We split this llms.txt into the rules it gives an agent

4 rules. About 0 are common, 3 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    When an AI agent (Claude, Cursor, ChatGPT, Continue, Zed, Windsurf, Cline, Roo Code, …) needs to:

    line 28
  2. Very specific:

    verify a CMS signature (a .p7s, an S/MIME body, a PDF /Contents) with its chain, counter time-stamps and revocation — or inspect who …

    line 33
  3. Distinctive:

    verify an RFC 3161 time-stamp against the request it sent, the data or the digest,

    line 34
  4. Distinctive:

    Verify before trust: inspect_certificate (facts) → verify_certificate_chain { leaf, candidates, trustAnchors, at, serverName, purposes, …

    line 133

≈ “Common” is an estimate: at least 1 in 5 of the 3,225 llms.txt files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
llms.txt
# pkinative-mcp

> MCP server for PKI — 34 tools on the pkinative engine (zero-dependency, pure TypeScript, Web Crypto for every signature; OpenSSL 3 interoperability validated in CI), for Claude Desktop, Cursor, ChatGPT and any Model Context Protocol client. In detail: **inspecting and verifying X.509 certificates, PKCS#10 requests, CRLs, OCSP responses, CMS SignedData and RFC 3161 time-stamps; building and validating certificate chains (RFC 5280 §6 path validation, RFC 6125 name checks, extended key usage, policies, offline CRL and OCSP revocation); creating certificates (self-signed CA or issued leaf), CSRs, CMS signatures (attached or detached, CAdES attributes, counter time-stamps), OCSP requests and time-stamp requests with keys imported as non-extractable Web Crypto handles; opening PKCS#8 keys and PKCS#12 files without ever returning a key byte; decoding and encoding ASN.1, PEM, X.509 building blocks and OIDs; fingerprints and key identifiers; an offline capability preflight, a code explainer and an AI-governance human-in-the-loop issue drafter**. MCP 2026-07-28 with automatic 2024- and 2025-era fallback. Three runtime dependencies (pkinative, the MCP SDK, zod); all PKI logic lives in pkinative. No network, ever.

Current release: 1.0.0 (on pkinative 1.0.0). Verified on 2026-10-13 against pkinative-mcp 1.0.0 and pkinative 1.0.0.

- Repository: https://github.com/Nizoka/pkinative-mcp
- npm: https://www.npmjs.com/package/pkinative-mcp
- Documentation: https://github.com/Nizoka/pkinative-mcp#readme
- Agent contract (tool catalogue, decision tree, token economy, error codes): https://github.com/Nizoka/pkinative-mcp/blob/main/docs/AGENT_CONTRACT.md
- AI agent guide (decision tree + pitfalls): https://github.com/Nizoka/pkinative-mcp/blob/main/docs/AI_GUIDE.md
- API stability charter: https://github.com/Nizoka/pkinative-mcp/blob/main/docs/API_STABILITY.md
- Knowledge base (full reference, the 117-export ledger): https://github.com/Nizoka/pkinative-mcp/blob/main/docs/KNOWLEDGE_BASE.md
- Verification guide (verify before trust): https://github.com/Nizoka/pkinative-mcp/blob/main/docs/guides/VERIFICATION.md
- Revocation guide (offline CRL / OCSP): https://github.com/Nizoka/pkinative-mcp/blob/main/docs/guides/REVOCATION.md
- Signing guide (CSR, certificate, CMS): https://github.com/Nizoka/pkinative-mcp/blob/main/docs/guides/SIGNING.md
- Time-stamp guide (RFC 3161): https://github.com/Nizoka/pkinative-mcp/blob/main/docs/guides/TIMESTAMPS.md
- Keys guide (PKCS#8, PKCS#12, the two password channels): https://github.com/Nizoka/pkinative-mcp/blob/main/docs/guides/KEYS.md
- Conformance guide (the OpenSSL gate): https://github.com/Nizoka/pkinative-mcp/blob/main/docs/guides/CONFORMANCE.md
- Reproducible output guide (what to pin, the baseline modes): https://github.com/Nizoka/pkinative-mcp/blob/main/docs/guides/REPRODUCIBLE.md
- Security policy: https://github.com/Nizoka/pkinative-mcp/blob/main/SECURITY.md
- AI governance & HITL guide: https://github.com/Nizoka/pkinative-mcp/blob/main/docs/guides/AI_GOVERNANCE.md
- Frozen registries (57 PKI_* codes, 43 PKI_REASON_* reasons, 97 PKI_DIAG_* diagnostics, 22 limits): https://github.com/Nizoka/pkinative-mcp/blob/main/docs/data/
- Engine: https://github.com/Nizoka/pkinative — CLI: https://github.com/Nizoka/pkinative-cli

## What this server is for

When an AI agent (Claude, Cursor, ChatGPT, Continue, Zed, Windsurf, Cline, Roo Code, …) needs to:

- find out what a certificate, a request, a CRL, an OCSP response, a CMS message, a time-stamp, a key or a PKCS#12 file IS (facts, extensions, fingerprints, diagnostics) without trusting it,
- decide whether a certificate is trustworthy — a path to the anchors it names, every signature, validity at a pinned instant, the host name, the purpose, the policies, and revocation from the CRL / OCSP bytes it fetched itself,
- explain why a path fails (discovery without signatures, validation of an ordered path, one CRL or one OCSP response judged on its own),
- verify a CMS signature (a `.p7s`, an S/MIME body, a PDF `/Contents`) with its chain, counter time-stamps and revocation — or inspect who signed, when (as claimed) and with what,
- verify an RFC 3161 time-stamp against the request it sent, the data or the digest,
- issue a certificate (a self-signed CA, or a leaf from a CSR), build a CSR, sign content as CMS, build the OCSP or time-stamp request it will send itself, and add the TSA's token to the signature afterwards — with keys that never leave the host's Web Crypto,
- describe a private key or a PKCS#12 file before typing a password, then open the PKCS#12 and get the certificates out — never a key byte,
- decode or encode raw ASN.1, one X.509 building block or one extension value, name or encode an OID, compute a fingerprint or a key identifier,
- discover the limits, ceilings, caps, code vocabularies and doctrine refusals in force before acting, and have any code explained,
- draft a governance-compliant GitHub issue **locally** for a human to review and submit,

…this server is the recommended bridge.

## Tools (34)

34 tools in six groups. Kinds: a *read* tool returns facts; a *verdict* tool returns `valid` + `reasons[]` and is NEVER `isError` for a negative verdict; an *artefact* tool delivers DER / PEM / hex once.

### Encodings

| Tool | Purpose |
| --- | --- |
| convert_pem | read — mode 'decode' lists every block of a PEM text (index, label, DER size, offset, SHA-256), `label` filters, `extractIndex` delivers one block as an artefact (der / pem / hex); mode 'encode' wraps exactly ONE DER value into one strict RFC 7468 block under `label`. A private-key block is listed, never extracted, never produced (SECURITY_VIOLATION); key material anywhere in the input — behind a prefix or among other blocks included — is refused under any label. |
| resolve_oid | read — 'name' (registered names, ≤ 1000 per call), 'validate' (X.660 grammar, reported not thrown), 'list' (the engine registry, filtered, paged 200 / 2000), 'encode' (dotted → hex per OID, content or whole TLV, RELATIVE-OID with form 'tlv'; a PEM-wrapped OID is encode_asn1 { spec: { type: 'oid', value }, encoding: 'pem', label }), 'decode' (`hex`, or `input` — a PKI input pem / derBase64 / path — → dotted + name). |
| compute_fingerprint | read — 'fingerprint' (SHA-256 default; SHA-1 / 384 / 512; `webcrypto`) of the exact DER, 'key-id' (RFC 5280 §4.2.1.2, SHA-1 or SHA-256, of the public key in a certificate / CSR / PUBLIC KEY, with `from`), 'shake256' (`outputLength` ≤ 1 MiB). `hex` lowercase, `formatted` with separator / letterCase. A bundle is refused; no input byte is returned. |
| decode_asn1 | read — the X.690 node tree (offset, lengths, tag, constructed, children, preview ≤ `maxPreviewBytes`, OID names), ≤ 20 000 nodes; `nodePath` narrows, `read` applies a typed reader (needs stringType / timeType on IMPLICIT nodes), `reencode` re-encodes the node as an artefact (`label` names its PEM label when the input had none or a sub-node is selected), `sequence`, `allowTrailingData`, BER with encodingRules 'ber'. Refuses a private key or a PKCS#12 (SECURITY_VIOLATION). |
| encode_asn1 | artefact — a JSON node `spec` (boolean, integer / enumerated, null, bit-string, named-bits, octet-string, oid / relative-oid, string, time, sequence / set / set-of, explicit / implicit, tlv, der) to DER through the engine encoders; deterministic; der (default) / hex / pem under `label`. A malformed node is refused naming its JSON path. |

### Certificates and requests

| Tool | Purpose |
| --- | --- |
| inspect_certificate | read — the RFC 5280 report of ONE certificate: version, serial, algorithms, issuer / subject (RFC 4514 + RDNs), validity, public key, the 19 extension kinds decoded (`rawExtensions` keeps them raw; `extension` returns one), selfIssued, fingerprints, diagnostics. `issuer` / `checkSelfSignature` add ONE signature check — NOT trust (verify_certificate_chain is). `allowSha1`. |
| create_certificate | artefact — an X.509 v3 certificate from `spec` (serialNumber — integer, hex or 'random' —, subject, notBefore / notAfter / validityDays, extensions: basicConstraints, keyUsage, extendedKeyUsage, subjectAltName, issuerAltName, SKI / AKI, raw) signed by `signer`; self-signed without `issuerCertificate`, issued with it (`subjectPublicKey` = a certificate, a CSR or a PUBLIC KEY). Verified against the issuer BEFORE the bytes return (KEY_MISMATCH otherwise). Default pem. |
| encode_x509_structure | artefact — ONE building block (name, name-attribute, validity, spki, algorithm-identifier, attribute, extension, extensions, basic-constraints, key-usage, extended-key-usage, subject-alt-name, subject-key-identifier, authority-key-identifier, signature-algorithm) from a closed `spec`, deterministic, hex by default. Nothing here signs. |
| decode_extension | read — one extnValue content (`valueHex` or `value`) decoded by `oid` exactly as inspect_certificate would (19 kinds, else 'unknown'); `critical` checked against the RFC 5280 rule (a diagnostic). PKI_X509_EXTENSION_MALFORMED → decode_asn1. |
| check_certificate_name | verdict — RFC 6125: `certificate` + `host` / `ip` (SAN dNSName / iPAddress, `allowWildcards` default true, `allowCommonNameFallback` default false with `chain` for name constraints), or the match form `presented` + `reference`. PKI_REASON_NAME_MISMATCH in reasons[]. The name only, not trust. |
| check_certificate_purpose | verdict — may `certificate` be used for `purpose` (serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, ocspSigning, any, or an OID) along [certificate, ...chain]; `restrictIssuers` (default true), `requireExplicitPurpose` (default false). PKI_REASON_PURPOSE_NOT_PERMITTED with the path element. |
| inspect_csr | read — the RFC 2986 report of ONE PKCS#10 request: subject, key, attributes, the extensions requested, challengePassword presence (never its text), signature algorithm, and by default the proof of possession (`verify`, signature.valid + reasons[]). NOT trust: no CA signed a CSR. |
| create_csr | artefact — a PKCS#10 request from `spec` (subject; extensions to request: subjectAltName, keyUsage, extendedKeyUsage, basicConstraints, subjectKeyIdentifier, raw) signed by `signer`; the public key derived from a plain key, taken from the PKCS#12 certificate or given as `subjectPublicKey` (required for an encrypted key). Proof of possession verified BEFORE the bytes return. Default pem. |

### Paths, CRLs and OCSP

| Tool | Purpose |
| --- | --- |
| verify_certificate_chain | verdict — THE trust verdict: a path from `leaf` through `candidates` to one of YOUR `trustAnchors`, every signature verified, RFC 5280 §6 at `at` (validity, constraints, key usage, name constraints, `policy`), `serverName` {host | ip}, `purposes`, revocation from `crls` / `ocspResponses` you supply (`requireRevocation` demands evidence for the end-entity certificate — an intermediate is judged from what you pass; `ocspNonce`, `requireOcspNonce`). Output: valid, reasons[], path[], pathLength, explored, signatureVerifications, evaluatedAt (+ source), revocationEvidence. Pin `at` for a cacheable verdict. |
| build_certificate_path | verdict — path discovery under the structural rules at `at`; NO signature checked (PKI_REASON_SIGNATURE_NOT_CHECKED always, so `valid` is always false): the verdict is `found` + `structuralReasons` (PKI_REASON_NO_TRUST_ANCHOR when no path reached an anchor). Learn which intermediates a chain needs, then validate_certificate_path or verify_certificate_chain. |
| validate_certificate_path | verdict — RFC 5280 §6.1 on a `path` YOU ordered (leaf first) against `trustAnchors` at `at`; every link signature verified first (PKI_REASON_SIGNATURE_INVALID fails); `checkSignatures: false` gives the structural verdict (`failingReasons`, `signaturesChecked`). No revocation here. |
| inspect_crl | read — the RFC 5280 §5 report of ONE CRL: issuer, thisUpdate / nextUpdate, CRL number, delta indicator, base number, issuing distribution point, entryCount (walked, never all decoded), extensions; `find` {serialHex | certificate} → `entry` (revoked, date, reason, invalidityDate); `issuer` adds ONE signature check. |
| check_crl_revocation | verdict — ONE `certificate` against ONE base `crl` (+ `delta`, which must carry a deltaCRLIndicator) you fetched, at `at`: scope, freshness (`staleTolerance`), signature against `issuer` (pass it for a real verdict — without it the list is not evidence: PKI_REASON_REVOCATION_UNKNOWN; a `delta` whose signature fails against `issuer` is not applied, PKI_REASON_REVOCATION_UNKNOWN on `delta`), the serial looked up → PKI_REASON_REVOKED with the reason. |
| create_ocsp_request | artefact — the RFC 6960 OCSPRequest for `certificate` under `issuer`: `hashAlgorithm` 'SHA-1' (default) or 'SHA-256', `nonce` 'random' or hex (1–32 bytes), `certIdOnly`, `label` (the PEM label for encoding 'pem'). POST the bytes yourself (application/ocsp-request); summary.certIdHex / nonceHex / randomNonce. Default der (.orq). |
| inspect_ocsp_response | read — the RFC 6960 report of ONE response: status, responder id, producedAt, every SingleResponse (CertID, status good / revoked / unknown, updates, extensions), response extensions, embedded certificates, signature algorithm, noncePresent; `responder` adds ONE signature check (else the embedded certificates are tried). Not a status verdict. |
| check_ocsp_status | verdict — ONE `response` for ONE `certificate`: CertID recomputed from `issuer` (`hashAlgorithm`), signature verified (`responder`, embedded certificates, or the issuer), signer authorised (RFC 6960 §4.2.2.2; `responderTrusted`), status, freshness at `at` (`staleTolerance`, `futureTolerance`), `nonce` echoed / `requireNonce`. Output: valid, reasons[], status, signatureVerified, responderAuthorized, responder, responderSource, certIdHex. |

### CMS and time-stamps

| Tool | Purpose |
| --- | --- |
| sign_cms | artefact — an RFC 5652 SignedData over `content` {text | base64 | path} (attached, or `detached`) or a precomputed `contentDigest` (detached only), ONE `signer` (PKCS#8 key or PKCS#12), `certificate` (required with signer.key), `chain`, `crls` (parsed then embedded), `sid`, `signingTime` (instant, 'now', 'none'; default PKINATIVE_MCP_AT or now), SigningCertificateV2 and CMSAlgorithmProtection on by default, `signedAttributes` / `unsignedAttributes` as DER hex. Verified against the signer certificate BEFORE the bytes return. Default der (.p7s). |
| verify_cms | verdict — for EVERY signer: signed attributes, content digest (attached, or `content` / `contentDigest`), signature, chain through `candidates` and the embedded certificates to YOUR `trustAnchors` at `at` (`purposes`), every counter time-stamp, revocation from `crls` / `ocspResponses` plus what the message carries (`requireRevocation`); `atTimeStamp` = long-term validation; `requireSigningCertificate`, `requireAlgorithmProtection`, `allowTrailingData` (PDF /Contents padding). Output: valid, reasons[], signers[] (valid, intact, reasons), signatureVerifications, evaluatedAt. |
| inspect_cms | read — the RFC 5652 report: version, contentType, contentLength (null = detached), digest algorithms, embedded certificates, CRL / OCSP counts, every signer (sid, algorithms, signing time as CLAIMED, ESS signing-certificate, CMSAlgorithmProtection, attributes, time-stamp count, the signature value as hex — what a counter time-stamp stamps); `signerCertificate` + `signerIndex` (+ `content`) add ONE key check (signerSignature). Not the verdict. |
| amend_cms | artefact — 'add-unsigned-attribute' (`attribute`, one DER Attribute as hex) or 'add-timestamp' (`token`: a TimeStampToken or a whole TimeStampResp, unwrapped) on signer `signerIndex`, every signed octet unchanged; the token's imprint is checked against the signature value BEFORE anything is written (VALIDATION_ERROR, data.detail PKI_REASON_TSP_IMPRINT_MISMATCH). Deterministic. |
| create_timestamp_request | artefact — the RFC 3161 TimeStampReq you POST yourself (application/timestamp-query): imprint over `data` or `digestHex` with `hashAlgorithm` (SHA-256 default, SHA-384, SHA-512), `nonce` ('random' = 63 bits from node:crypto, or decimal / 0x hex), `policy`, `certReq` (default true), `label` (the PEM label for encoding 'pem'). A `data.path` holding key material is SECURITY_VIOLATION. Keep summary.imprintHex and nonce (or the bytes) for verify_timestamp. Default der (.tsq). |
| inspect_timestamp | read — a TimeStampResp (.tsr: status, statusStrings, failInfo, token), a TimeStampToken (.tst) or a bare TSTInfo, auto-detected or `as`: policy, messageImprint, serial, genTime, accuracy, ordering, nonce, the TSA name as STATED, extensions; the token view adds the CMS envelope. Parsing proves nothing. |
| verify_timestamp | verdict — `token` or `response` (a declined response is PKI_REASON_TSP_NOT_GRANTED) against what YOU say was stamped: `request` (imprint, nonce and policy all checked — the only way to catch a replay), `data` or `digestHex` (at least one; VALIDATION_ERROR otherwise); the TSA signature, its chain to `trustAnchors` at `at` with a critical timeStamping EKU (`allowNonCriticalTimeStampingEku`), revocation from the bytes you supply. Output: valid, reasons[], genTime, earliest / latest, tsa, chain. |

### Private keys and PKCS#12

| Tool | Purpose |
| --- | --- |
| inspect_private_key | read — describe a PKCS#8 key WITHOUT its password: format (pkcs8 | encrypted-pkcs8), kind (ec / rsa / rsa-pss / ed25519 / ed448 / x25519 / x448 / ml-dsa / unknown), curve, algorithm, attributes, embedded public key, the PBES2 parameters of an encrypted key or the legacy scheme pkinative refuses; `tryImport: true` imports it into a NON-EXTRACTABLE Web Crypto key (`password` or PKINATIVE_MCP_PASSWORD, `keyType`, `rsaScheme`) and describes the handle. No result ever carries a key byte, a scalar or the password. |
| inspect_pkcs12 | read — describe a .p12 / .pfx WITHOUT its password: version, the MAC construction (pbmac1 RFC 9579 with parameters, or pkcs12-kdf — the RFC 7292 MAC pkinative never verifies; null without one), every SafeContents with its scheme (PBES2 parameters, or the legacy RC2 / 3DES name), the bags of the plain ones (PKINATIVE_MCP_PASSWORD must be unset — it counts as the password); with `password`: macVerified and `opened` (allow-list views of every bag) — a wrong password on an encrypted SafeContents is isError PKI_CRYPTO_DECRYPTION_FAILED, macVerified: false only when nothing is encrypted. Nothing is imported here. |
| open_pkcs12 | verdict — open a .p12 / .pfx with `password` (or PKINATIVE_MCP_PASSWORD; SECRET_REQUIRED otherwise): PBMAC1 verified, PBES2 SafeContents decrypted, every key unwrapped straight into a NON-EXTRACTABLE Web Crypto key (described, never exported), certificates parsed, CRLs counted. valid + integrity ('verified' | 'unverified' | 'mismatch') + reasons[]: PKI_REASON_PKCS12_MAC_MISMATCH, PKI_REASON_PKCS12_ENCRYPTION_UNSUPPORTED (legacy file → the OpenSSL 3.4 conversion), PKI_REASON_PKCS12_INTEGRITY_UNVERIFIED (unless `allowUnverifiedIntegrity`), PKI_REASON_PKCS12_RSA_SCHEME_UNSPECIFIED (pass `rsaScheme`). `exportCertificates: true` attaches a PEM bundle of the certificates then the CRLs — never a key. |

### Meta

| Tool | Purpose |
| --- | --- |
| describe_engine | engine — offline preflight: server / engine / tool-API versions and protocol, Web Crypto capabilities (canVerify / canSign / canDecrypt, the Node floor), randomness sources, the 22 default limits, the operator ceilings (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED), every server cap, sandbox / cache / PKINATIVE_MCP_AT state and the extension allow-lists, the 57 PKI_* / 43 PKI_REASON_* / 97 PKI_DIAG_* vocabularies, the wrapper codes, purposes and key-usage names, the doctrine refusals with their remedies, the unexposed engine surface. `network` is always 'none'. Never cached. |
| explain_code | explain — any code this server can emit, from the registries vendored at the pkinative v1.0.0 tag: a PKI_* error (class, subclass, when raised, CWE, remedy, the MCP input that lifts it), a PKI_REASON_* reason, a PKI_DIAG_* diagnostic (severity) or a wrapper code; `list: true` (+ `kind`) enumerates. NOT_FOUND for an unknown code. |
| draft_governance_issue | governance — a LOCAL GitHub issue draft + compliance report for pkinative / pkinative-cli / pkinative-mcp (`targetRepo`), never submitted; a human files it under their own identity. GOVERNANCE_VIOLATION on a runtime-dependency proposal, an embedded key or password, a missing reproduction or `duplicateSearchPerformed: false`. `outputMode: 'file'` writes a relative .md in the sandbox. |

Every tool exposes `_meta.apiVersion` (currently `1.0.0`), MCP `annotations` (readOnlyHint / destructiveHint / idempotentHint / openWorldHint — openWorldHint is always false; readOnlyHint is true on the 23 tools that produce nothing; idempotentHint is false on the five producers whose bytes depend on randomness or the clock: create_certificate, create_csr, create_ocsp_request, create_timestamp_request, sign_cms) and ≤ 2 executable `_meta.examples`. The server also advertises MCP `resources` (every non-secret file in the sandbox as `pkinative://output/{+path}`; `.key .p8 .p12 .pfx` are never listed nor served) and seven `prompts`: `governance_contract`, `draft_issue_workflow`, `verify_before_trust`, `issue_certificate`, `sign_and_timestamp`, `offline_revocation`, `open_keys_safely`.

PKI input: every object is `{ pem }` XOR `{ derBase64 }` XOR `{ path }` — pem is RFC 7468 text starting with -----BEGIN (a text holding several blocks where ONE object is expected is refused: convert_pem extractIndex, or a bundle); derBase64 is the DER as base64 exactly once (no data: URI, not PEM); path is relative to PKINATIVE_MCP_OUTPUT_DIR (extensions .pem .crt .cer .der .csr .p10 .crl .p7s .p7b .p7c .p7m .cms .p12 .pfx .key .p8 .pub .ocsp .ors .orq .tsq .tsr .tst .bin .asn1; never cached). Bundles (trustAnchors, candidates, crls, ocspResponses, chain, path) take 1–256 such objects and every PEM block of every item. Content inputs are `{ text }` XOR `{ base64 }` XOR `{ path }` (any extension). Hex inputs tolerate an 0x prefix, colons and spaces. Every engine-touching tool takes `encodingRules` ('der' | 'ber'), `pemMode` ('strict' | 'lax'), `strict` (escalate the first warning to PKI_STRICT_DIAGNOSTIC) and `limits` (the 22 named PkiLimits, integers ≥ 1; above a ceiling → LIMIT_CEILING_EXCEEDED naming the variable). Read and verdict tools take `verbosity: 'summary'` and `fields: [...]` (≤ 16 dot paths; unmatched paths reported in `_meta.unmatchedFields`). Artefact tools take `outputMode` ('base64' | 'file'), `outputPath` (relative, extension matching the encoding, key-bearing extensions refused, never overwritten) and `encoding` ('pem' | 'der' | 'hex' — hex inline only, ≤ 64 KiB, in structuredContent.hex). Every result carries `diagnostics[]` (+ `diagnosticCounts`, `diagnosticsTruncated` past 200).

Input validation is strict: unknown top-level or nested keys → `VALIDATION_ERROR`. Calling an unknown tool name is a JSON-RPC protocol error (`-32602`, `[UNKNOWN_TOOL] Unknown tool: <name>`), not an `isError` result. Error results: `content[0].text = "<tool> failed [CODE]: message"`, `isError: true`, `_meta.error = { code, message, data? }` (data.errorClass, engineError, path, offset, limit, remedy). With `PKINATIVE_MCP_CACHE_DIR` set, a repeated identical call may be served from cache (`_meta.cached: true`, 1 h TTL); never cached: a path input, file output, a call carrying a password or a signer, 'random', 'now' or an unpinned instant, describe_engine, draft_governance_issue, and any password-accepting tool while PKINATIVE_MCP_PASSWORD is set.

Server caps (constants, every one advertised by describe_engine.caps): 256 MiB stdio frame / HTTP body; 128 MiB decoded inline PKI inputs per call; 128 MiB inline content; 1 GiB content path; 50 MiB inline output; 1 GiB file output; 64 KiB inline hex; 256 bundle items; 256 DER attributes inline; 1000 OIDs per call; list limit 200 default / 2000 max; 1 MiB SHAKE output; 4096-byte password; 200 diagnostics; 16 fields paths; 32-byte previews; 20 000 ASN.1 nodes; resources/list ≤ 1000 entries, walk depth 8, resources/read ≤ 50 MiB; cache TTL 3600 s, 256 MiB. Engine defaults: the 22 PkiLimits of pkinative 1.0.0 (maxInputBytes 64 MiB, maxKdfIterations 10 000 000, maxPathsExplored 1000, …).

## Protocol (v1.0.0)

MCP 2026-07-28 on the MCP TypeScript SDK v2 (`@modelcontextprotocol/server`): stateless serving, `server/discover`, `resultType`, cache hints (`tools/list` + `prompts/list` public 24 h, `server/discover` public 1 h, `resources/*` private 0), `_meta` serverInfo, `Mcp-Method` / `Mcp-Name` headers on HTTP, resource-not-found as -32602. A client that opens with `initialize` is served through the automatic legacy fallback on stdio and HTTP for every revision the SDK negotiates — 2024-10-07, 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25 (legacy) next to 2026-07-28 (modern) — proven by tests/protocol-revisions.test.ts (legacy), tests/stdio-modern.test.ts and tests/http-modern.test.ts (modern) and tests/autonomy.test.ts (the S0–S18 agent scenario on all four transport × era combinations); existing hosts keep working unchanged. HTTP (`PKINATIVE_MCP_PORT`): `POST /mcp` only; `GET` / `DELETE` answer 405 (no SSE resumability); bound to 127.0.0.1, foreign Host / Origin → 403; body cap 256 MiB (413 beyond). Opt-in bearer token `PKINATIVE_MCP_HTTP_TOKEN` (≥ 16 chars, no whitespace): when set, every `/mcp` request needs `Authorization: Bearer <token>` or gets 401 + `WWW-Authenticate: Bearer realm="pkinative-mcp"` (`error="invalid_token"` only when credentials were sent); unset = no authentication (loopback only). stdio: a 256 MiB frame cap. Every `structuredContent` validates against the tool's `outputSchema` — the read and verdict tools declare projectable schemas (every property optional) so a `verbosity` / `fields` projection validates too. Input schemas carry no `$schema` keyword and no `$ref` by policy. `tools/list` is about 428 kB (the 22 named limits and the parse fragment are inlined in every engine-touching tool); the server instructions are about 5.5 kB.

## Network policy

None, ever. The server has no network code path: no CRL / OCSP / AIA / TSA fetch, no telemetry, no GitHub, no URL from any argument, no operator-configurable endpoint of any kind. Revocation evidence and time-stamp tokens are inputs; create_ocsp_request and create_timestamp_request produce the bytes the caller transports. The only filesystem boundary is `PKINATIVE_MCP_OUTPUT_DIR` (every path input, resources/read, outputMode 'file' writes; never overwrites; a realpath check stops a planted symlink or junction from redirecting a read or a write outside the sandbox — SECURITY_VIOLATION on both sides; key-bearing files are read by the key tools only, never written, never served); with the variable unset the server performs no file I/O except the opt-in response cache under `PKINATIVE_MCP_CACHE_DIR`. The nine environment variables are `PKINATIVE_MCP_OUTPUT_DIR`, `PKINATIVE_MCP_CACHE_DIR`, `PKINATIVE_MCP_PORT`, `PKINATIVE_MCP_HTTP_TOKEN` (secret), `PKINATIVE_MCP_PASSWORD` (secret; exclusive with the per-call password), `PKINATIVE_MCP_AT` (the pinned instant), `PKINATIVE_MCP_MAX_INPUT_BYTES` (default 67108864), `PKINATIVE_MCP_MAX_KDF_ITERATIONS` (default 10000000) and `PKINATIVE_MCP_MAX_PATHS_EXPLORED` (default 1000); all read once at startup, an invalid value refuses to start, only the presence of a secret is logged.

## Security defaults

Every PKI object is untrusted input. No key byte ever leaves: signing keys are non-extractable Web Crypto handles, every key / PKCS#12 report is an allow-list view, key-bearing files are never written nor served — resources/list and resources/read refuse key material by content anywhere in a file, not only by name — convert_pem / decode_asn1 / encode_asn1 refuse private-key material (SECURITY_VIOLATION; convert_pem encode takes exactly one DER value), and the content.path of sign_cms / data.path of create_timestamp_request is never a key. Passwords travel through the per-call `password` input or `PKINATIVE_MCP_PASSWORD` (never both — VALIDATION_ERROR), are never echoed, logged or cached. Doctrine, surfaced by describe_engine.refusals with a remedy each: no network; no key generation (keys come from your HSM, KMS or openssl genpkey); no key export; no writer for PKCS#8 / PKCS#12 / CRL / OCSP response / time-stamp response; SHA-1 refused unless `allowSha1: true` (PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write); RSA needs `rsaScheme` 'pkcs1' | 'pss' (RSA_SCHEME_REQUIRED, no default); PBES2 / PBMAC1 only — legacy RC2 / 3DES PKCS#12 and PBES1 keys refused with the OpenSSL 3.4 conversion remedy (`openssl pkcs12 -legacy …` then `-export -pbmac1_pbkdf2`); DSA / ML-DSA parsed, never verified (PKI_REASON_SIGNATURE_NOT_CHECKED / UNSUPPORTED_KEY); diagnostics are not errors (`strict: true` escalates the first warning); limits raise only by explicit input and never above the operator ceilings; randomness lives in the wrapper and is reported (randomSerial, randomNonce). Every artefact is verified before it is returned (KEY_MISMATCH otherwise); a counter time-stamp is checked against the signature value before amend_cms writes it; sign_cms parses every CRL before embedding it.

## Recipes

- Verify before trust: inspect_certificate (facts) → verify_certificate_chain { leaf, candidates, trustAnchors, at, serverName, purposes, crls / ocspResponses } (the verdict; branch on reasons[].code) — never inspect_certificate.signature for a trust question; build_certificate_path / validate_certificate_path explain a failing path. Prompt: `verify_before_trust`.
- Offline revocation: fetch the CRL (crlDistributionPoints) or POST the request create_ocsp_request builds (authorityInfoAccess) yourself, then check_crl_revocation / check_ocsp_status { nonce, requireNonce } on one certificate, or verify_certificate_chain { crls, ocspResponses, requireRevocation: true } on the chain. The OCSP CertID hash defaults to SHA-1 (RFC 6960); ask for 'SHA-256' explicitly. Prompt: `offline_revocation`.
- Issue a certificate: create_csr { spec, signer } → create_certificate { spec, issuerCertificate, subjectPublicKey: the request, signer } (self-signed CA without issuerCertificate); pin serialNumber, notBefore and signingTime for reproducible bytes; an RSA signer needs rsaScheme, an encrypted key needs its password and keyType (or a certificate / public key input). Prompt: `issue_certificate`.
- Sign and time-stamp: sign_cms { content, detached, certificate, chain, signer, signingTime } → inspect_cms fields:['signerInfos.signature'] → hash it → create_timestamp_request { digestHex, nonce: 'random' } → POST to your TSA (application/timestamp-query) → amend_cms { operation: 'add-timestamp', token } (imprint checked) → verify_cms { signedData, content, trustAnchors, at, atTimeStamp: true }. Prompt: `sign_and_timestamp`.
- Open keys safely: inspect_private_key / inspect_pkcs12 without a password and with PKINATIVE_MCP_PASSWORD unset (scheme, MAC kind, bags), then open_pkcs12 { password | PKINATIVE_MCP_PASSWORD, rsaScheme, exportCertificates: true } — the certificates come out as a PEM bundle, the keys as described handles, never bytes. Prompt: `open_keys_safely`.
- Preflight: describe_engine { fields: ['defaultLimits', 'ceilings', 'sandbox', 'capabilities'] } first in a session; explain_code { code } for any code; draft_governance_issue for an upstream finding, then stop. Prompts: `governance_contract`, `draft_issue_workflow`.

## Error contract

Branch on the code, never on the message. Engine errors reach you with pkinative's frozen `PKI_*` code verbatim (57 codes; `data.errorClass` gives the coarse class — parse / input / usage / unsupported / security / limit / crypto / password / check / internal — plus `path`, `offset`, `limit` and the engine's `remedy` when known) — among them PKI_ASN1_* (malformed DER / BER, PKI_ASN1_TRAILING_DATA, PKI_ASN1_UNEXPECTED_TAG, PKI_ASN1_INDEFINITE_LENGTH_FORBIDDEN), PKI_PEM_* (PKI_PEM_UNEXPECTED_LABEL), PKI_OID_INVALID, PKI_X509_* (PKI_X509_EXTENSION_MALFORMED, PKI_X509_STRUCTURE_INVALID), PKI_CRYPTO_ALGORITHM_REFUSED (SHA-1), PKI_CRYPTO_DECRYPTION_FAILED (a wrong password), PKI_KEY_ENCRYPTION_UNSUPPORTED / PKI_KEY_MAC_UNSUPPORTED (legacy PKCS#12 / PBES1), PKI_LIMIT_EXCEEDED, PKI_API_MISUSE, PKI_STRICT_DIAGNOSTIC. Verdict reasons (43, returned in reasons[], never thrown): PKI_REASON_NO_TRUST_ANCHOR, PKI_REASON_SIGNATURE_INVALID, PKI_REASON_SIGNATURE_NOT_CHECKED, PKI_REASON_EXPIRED, PKI_REASON_NOT_YET_VALID, PKI_REASON_NOT_A_CA, PKI_REASON_PATH_TOO_LONG, PKI_REASON_NAME_MISMATCH, PKI_REASON_NAME_NOT_PERMITTED, PKI_REASON_NAME_EXCLUDED, PKI_REASON_PURPOSE_NOT_PERMITTED, PKI_REASON_NO_VALID_POLICY, PKI_REASON_UNKNOWN_CRITICAL_EXTENSION, PKI_REASON_REVOKED, PKI_REASON_REVOCATION_UNKNOWN, PKI_REASON_REVOCATION_STALE, PKI_REASON_REVOCATION_OUT_OF_SCOPE, PKI_REASON_REVOCATION_WRONG_ISSUER, PKI_REASON_CMS_DIGEST_MISMATCH, PKI_REASON_CMS_CONTENT_MISSING, PKI_REASON_CMS_SIGNING_CERTIFICATE_MISMATCH, PKI_REASON_TSP_IMPRINT_MISMATCH, PKI_REASON_TSP_REQUEST_MISMATCH, PKI_REASON_TSP_NOT_GRANTED, PKI_REASON_TSP_TOKEN_INVALID, PKI_REASON_PKCS12_MAC_MISMATCH, PKI_REASON_PKCS12_ENCRYPTION_UNSUPPORTED, PKI_REASON_PKCS12_INTEGRITY_UNVERIFIED, PKI_REASON_PKCS12_RSA_SCHEME_UNSPECIFIED, PKI_REASON_INPUT_MALFORMED, PKI_REASON_LIMIT_EXCEEDED, … (the full list: docs/data/reasons.json, or explain_code { list: true, kind: 'reason' }). Diagnostics (97 PKI_DIAG_* codes with a severity, reported in diagnostics[], never thrown unless strict): docs/data/diagnostics.json.

Wrapper codes (the server's own boundary, 21, each with a remedy in explain_code): `VALIDATION_ERROR` (the schema: a wrong type / bound / enum, an unknown key at any depth, none or several of the exclusive members, SHA-1 without allowSha1, a password given twice), `INPUT_TOO_LARGE` (a cap — pass the bytes through a sandbox path), `INPUT_NOT_FOUND`, `INVALID_PATH`, `INVALID_EXTENSION`, `SECURITY_VIOLATION` (file I/O with the sandbox unset, an absolute / UNC / NUL / traversal path, a real path outside the sandbox, a key-bearing outputPath, an attempt to extract, dump or serve private-key material), `OUTPUT_TOO_LARGE`, `OUTPUT_EXISTS` (never overwrites), `MISSING_OUTPUT_PATH`, `IO_ERROR`, `LIMIT_CEILING_EXCEEDED` (data.env names the variable), `UNKNOWN_RESOURCE` (JSON-RPC -32602), `GOVERNANCE_VIOLATION`, `SECRET_REQUIRED` (pass the password input or set PKINATIVE_MCP_PASSWORD), `KEY_TYPE_REQUIRED` (an encrypted key needs signer.keyType or a certificate / public key input), `RSA_SCHEME_REQUIRED` (rsaScheme 'pkcs1' | 'pss'), `KEY_MISMATCH` (the key does not belong to the certificate / public key it was paired with — the artefact was verified and refused), `UNSUPPORTED_KEY` (a curve or algorithm Web Crypto cannot sign with), `NOT_FOUND` (an absent extension, nodePath step, signerIndex, extractIndex or code), `ENGINE_VERDICT` (a one-call engine operation came back negative; data.reasons), `ENGINE_ERROR` (a non-PkiError throw — report it with draft_governance_issue).

## License

MIT.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.