Skip to main content
Glama
Nizoka

pkinative-mcp

pkinative-mcp

MCP server for PKI — inspect and verify X.509 certificates, CSRs, CRLs, OCSP responses, CMS SignedData and RFC 3161 time-stamps; build and validate certificate chains (RFC 5280 path validation, name and purpose checks, offline CRL and OCSP revocation); create certificates, CSRs, CMS signatures, OCSP and time-stamp requests; open PKCS#8 and PKCS#12 without ever returning a key byte; decode ASN.1, PEM and OIDs — 34 tools and 7 prompts 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. No network, ever.

npm version npm downloads Node version License: MIT CI OpenSSL interoperability MCP pkinative TypeScript OpenSSF Scorecard CodeQL


✨ Features

pkinative-mcp exposes 34 tools to any MCP host, in six groups. The kind says how the result is shaped: a read tool returns facts (structuredContent, projectable with verbosity / fields); a verdict tool returns valid + reasons[] and is never isError for a negative verdict; an artefact tool delivers DER or PEM once, inline or into the sandbox. The last column names the refusal codes an agent branches on (the full table is in docs/AGENT_CONTRACT.md).

Encodings

Tool

Kind

What it does

Key refusal codes

convert_pem

read

mode: 'decode' lists every block of a PEM text (label, DER size, offset, SHA-256) and extractIndex delivers one as 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 and never produced; key material anywhere in the input is refused.

SECURITY_VIOLATION, NOT_FOUND, VALIDATION_ERROR, PKI_PEM_UNEXPECTED_LABEL, PKI_PEM_*

resolve_oid

read

Object identifiers in one call: name (registered names, ≤ 1000 per call), validate (X.660 grammar, reported never thrown), list (the engine registry, paged 200 / 2000), encode (dotted → hex, content or whole TLV, RELATIVE-OID), decode (hex → dotted + name).

VALIDATION_ERROR, PKI_OID_INVALID, PKI_ASN1_*

compute_fingerprint

read

fingerprint of the exact DER (SHA-256 default; SHA-1 / 384 / 512), key-id (the RFC 5280 §4.2.1.2 identifier of the public key of a certificate, CSR or PUBLIC KEY), shake256 (outputLength bytes, the Ed448 CMS digest). Any object may be digested; no input byte is returned.

VALIDATION_ERROR, PKI_X509_*, PKI_ASN1_*

decode_asn1

read

The X.690 node tree of any DER (BER with encodingRules: 'ber'): offsets, tags, previews, OID names, ≤ 20 000 nodes; nodePath narrows to a subtree, read applies a typed reader, reencode: true re-encodes the node as an artefact. Refuses a private key or a PKCS#12 (by label, or when the DER parses as one).

SECURITY_VIOLATION, NOT_FOUND, PKI_ASN1_UNEXPECTED_TAG, PKI_API_MISUSE, PKI_ASN1_*

encode_asn1

artefact

A JSON node spec (boolean, integer, bit-string, octet-string, oid, string, time, sequence / set / set-of, explicit / implicit, tlv, der) encoded to DER through the engine's own encoders — deterministic; the inverse of decode_asn1.

VALIDATION_ERROR (naming the JSON path), SECURITY_VIOLATION, PKI_LIMIT_EXCEEDED, PKI_ASN1_*, PKI_OID_INVALID

Certificates and requests

Tool

Kind

What it does

Key refusal codes

inspect_certificate

read

The RFC 5280 report of one certificate: serial, algorithms, issuer / subject (RFC 4514 + RDNs), validity, public key, the 19 extension kinds decoded (rawExtensions: true keeps them raw), fingerprints, diagnostics; extension returns one; issuer / checkSelfSignature adds one signature check — which is not trust.

NOT_FOUND, PKI_PEM_UNEXPECTED_LABEL, PKI_X509_*, PKI_CRYPTO_ALGORITHM_REFUSED

create_certificate

artefact

Issue an X.509 v3 certificate from spec (subject, validity, extensions), signed by signer (a PKCS#8 key or a PKCS#12 file imported as a non-extractable Web Crypto key): self-signed CA, or a leaf issued from a certificate / CSR / PUBLIC KEY. Serial 'random' by default; verified against the issuer before the bytes return.

KEY_MISMATCH, RSA_SCHEME_REQUIRED, KEY_TYPE_REQUIRED, SECRET_REQUIRED, UNSUPPORTED_KEY, VALIDATION_ERROR

encode_x509_structure

artefact

One X.509 building block (name, validity, spki, algorithm-identifier, extension(s), basic-constraints, key-usage, extended-key-usage, subject-alt-name, SKI / AKI, signature-algorithm) encoded deterministically from a closed spec; hex by default. Nothing here signs.

VALIDATION_ERROR (naming the member), KEY_TYPE_REQUIRED, SECRET_REQUIRED, RSA_SCHEME_REQUIRED

decode_extension

read

One extnValue content (hex or DER) decoded by its OID exactly as inspect_certificate would; critical is checked against the kind's RFC 5280 rule (a diagnostic, not a refusal).

PKI_X509_EXTENSION_MALFORMED, VALIDATION_ERROR

check_certificate_name

verdict

The RFC 6125 identity verdict: certificate + host / ip (SAN dNSName / iPAddress, wildcards, an opt-in CN fallback held to the chain's name constraints), or the match form presented + reference with no certificate.

PKI_REASON_NAME_MISMATCH (in reasons[]), VALIDATION_ERROR

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) applies a CA's own EKU to what it issued.

PKI_REASON_PURPOSE_NOT_PERMITTED (in reasons[]), VALIDATION_ERROR

inspect_csr

read

The RFC 2986 report of one PKCS#10 request: subject, key, attributes, the extensions requested, whether a challengePassword is present (never its text), and by default the proof of possession (signature.valid).

PKI_REASON_SIGNATURE_NOT_CHECKED (in reasons[]), PKI_X509_*, PKI_PEM_UNEXPECTED_LABEL

create_csr

artefact

A PKCS#10 request from spec (subject, the extensions to request) signed by signer; the public key is derived from a plain key, taken from the PKCS#12 certificate or given as subjectPublicKey; the proof of possession is verified before the bytes return.

KEY_MISMATCH, RSA_SCHEME_REQUIRED, SECRET_REQUIRED, KEY_TYPE_REQUIRED, VALIDATION_ERROR

Paths, CRLs and OCSP

Tool

Kind

What it does

Key refusal codes

verify_certificate_chain

verdict

The one-call trust verdict: a path from leaf through candidates to one of your trustAnchors, every signature verified, RFC 5280 §6 validation at at (validity, constraints, key usage, names, policies), serverName (RFC 6125) and purposes, and revocation from the CRL / OCSP bytes you supply (requireRevocation demands evidence for the end-entity certificate).

PKI_REASON_* in reasons[] (NO_TRUST_ANCHOR, EXPIRED, NOT_YET_VALID, REVOKED, REVOCATION_UNKNOWN, NAME_MISMATCH, …), VALIDATION_ERROR

build_certificate_path

verdict

Path discovery only: which intermediates lead from leaf to an anchor under the structural rules at at. No signature is checked — valid is always false, the verdict is found and structuralReasons.

PKI_REASON_SIGNATURE_NOT_CHECKED (always), PKI_REASON_NO_TRUST_ANCHOR

validate_certificate_path

verdict

RFC 5280 §6.1 validation of a path you ordered (leaf first) against your anchors at at; every link signature verified first (checkSignatures: false gives the structural verdict with failingReasons). No revocation here.

PKI_REASON_SIGNATURE_INVALID, PKI_REASON_SIGNATURE_NOT_CHECKED, VALIDATION_ERROR

inspect_crl

read

The RFC 5280 §5 report of one CRL: issuer, updates, CRL number, delta indicator, IDP, entry count (walked, never all decoded), extensions; find looks one serial up; issuer adds one signature check.

VALIDATION_ERROR, PKI_X509_*, PKI_CRYPTO_ALGORITHM_REFUSED

check_crl_revocation

verdict

One certificate against one base crl (and an optional delta) you fetched, at at: scope, freshness (staleTolerance), the signature verified against issuer, the serial looked up. A delta whose signature does not verify against issuer is not applied: valid: false with PKI_REASON_REVOCATION_UNKNOWN on delta.

PKI_REASON_REVOKED, PKI_REASON_REVOCATION_UNKNOWN (in reasons[]), VALIDATION_ERROR (a delta without deltaCRLIndicator)

create_ocsp_request

artefact

The DER OCSPRequest (RFC 6960) for certificate under issuer: CertID with SHA-1 (default) or SHA-256, an RFC 8954 nonce ('random' or hex), certIdOnly. You POST the bytes to the responder; summary.nonceHex / certIdHex match the answer.

VALIDATION_ERROR

inspect_ocsp_response

read

The RFC 6960 report of one response: status, responder id, producedAt, every SingleResponse (CertID, status, updates), extensions, embedded certificates, nonce presence; responder adds one signature check.

PKI_* (PKI_ASN1_*, PKI_X509_*), VALIDATION_ERROR

check_ocsp_status

verdict

One response for one certificate: the CertID recomputed from issuer, the signature verified (responder, the embedded certificates or the issuer), the signer authorised (RFC 6960 §4.2.2.2), status, freshness at at, nonce (nonce, requireNonce).

PKI_REASON_REVOKED and the other PKI_REASON_* in reasons[], VALIDATION_ERROR

CMS and time-stamps

Tool

Kind

What it does

Key refusal codes

sign_cms

artefact

An RFC 5652 SignedData over content (attached or detached) or a precomputed contentDigest, one signer (PKCS#8 key or PKCS#12), certificate, chain and crls parsed then embedded; signed attributes always written (contentType, messageDigest, signingTime, SigningCertificateV2, CMSAlgorithmProtection); verified against the signer certificate before the bytes return.

KEY_MISMATCH, RSA_SCHEME_REQUIRED, SECRET_REQUIRED, VALIDATION_ERROR, PKI_API_MISUSE, ENGINE_ERROR

verify_cms

verdict

The one-call verdict on a SignedData: for every signer the signed attributes, the content digest (attached, or content / contentDigest), the signature, the chain to your trustAnchors at at, every counter time-stamp, and revocation from the bytes you supply plus those the message carries; atTimeStamp: true is long-term validation.

PKI_REASON_* in reasons[] (per signer too; intact tells unaltered from untrusted), PKI_ASN1_TRAILING_DATA, VALIDATION_ERROR

inspect_cms

read

The RFC 5652 report: content type and length, digest algorithms, embedded certificates, CRL / OCSP counts, every signer (identifier, algorithms, signing time as claimed, attributes, time-stamp count, the signature value a counter time-stamp stamps); signerCertificate adds one key check on the signer signerIndex names.

NOT_FOUND (a signerIndex past the last signer, with signerCertificate), PKI_ASN1_TRAILING_DATA, PKI_*

amend_cms

artefact

The one edit a signed message legitimately receives afterwards: add-unsigned-attribute (a DER Attribute as hex) or add-timestamp (a TimeStampToken or the whole TimeStampResp) 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), NOT_FOUND, PKI_API_MISUSE

create_timestamp_request

artefact

The RFC 3161 TimeStampReq you POST to a TSA yourself: imprint over data or digestHex (SHA-256 / 384 / 512), nonce ('random', decimal or hex), policy, certReq.

VALIDATION_ERROR (a digest of the wrong length)

inspect_timestamp

read

A TimeStampResp, a TimeStampToken or a bare TSTInfo (auto-detected, or as): status, policy, imprint, serial, genTime, accuracy, nonce, the TSA name as stated, the CMS envelope. Parsing proves nothing.

PKI_*, PKI_STRICT_DIAGNOSTIC (with strict: true)

verify_timestamp

verdict

token or response judged against what you say was stamped (request, data or digestHex — at least one), the TSA signature, its chain to your anchors at at with a critical timeStamping EKU, and revocation from the bytes you supply.

PKI_REASON_TSP_IMPRINT_MISMATCH, PKI_REASON_TSP_NOT_GRANTED and the other PKI_REASON_* in reasons[], VALIDATION_ERROR

Private keys and PKCS#12

Tool

Kind

What it does

Key refusal codes

inspect_private_key

read

Describe a PKCS#8 key without its password (with PKINATIVE_MCP_PASSWORD unset — the variable counts as the password of every password-accepting tool): format, kind, curve, algorithm, the PBES2 parameters of an encrypted key (or the legacy scheme pkinative refuses); tryImport: true imports it as a non-extractable Web Crypto key and reports the handle. No result ever carries a key byte.

SECRET_REQUIRED, KEY_TYPE_REQUIRED, RSA_SCHEME_REQUIRED, PKI_CRYPTO_DECRYPTION_FAILED, PKI_KEY_ENCRYPTION_UNSUPPORTED

inspect_pkcs12

read

Describe a .p12 / .pfx without its password (the operator's PKINATIVE_MCP_PASSWORD counts as one, so it must be unset): MAC construction (PBMAC1, or the legacy RFC 7292 MAC), every SafeContents and its scheme, the bags of the plain ones; with a password, macVerified and opened (allow-list views of every bag) — a wrong password on an encrypted SafeContents is PKI_CRYPTO_DECRYPTION_FAILED.

PKI_CRYPTO_DECRYPTION_FAILED, PKI_KEY_MAC_UNSUPPORTED, PKI_KEY_ENCRYPTION_UNSUPPORTED

open_pkcs12

verdict

Open a PKCS#12 in one call: MAC verified, PBES2 SafeContents decrypted, every key unwrapped straight into a non-extractable Web Crypto key (described, never exported), certificates parsed, CRLs counted; exportCertificates: true attaches a PEM bundle of the certificates and CRLs — never a key.

SECRET_REQUIRED; in reasons[]: PKI_REASON_PKCS12_MAC_MISMATCH, PKI_REASON_PKCS12_ENCRYPTION_UNSUPPORTED, PKI_REASON_PKCS12_INTEGRITY_UNVERIFIED, PKI_REASON_PKCS12_RSA_SCHEME_UNSPECIFIED

Meta

Tool

Kind

What it does

Key refusal codes

describe_engine

engine

Offline preflight: versions, the host's Web Crypto capabilities, the engine's 22 default limits, the operator ceilings, every server cap, sandbox / cache / pinned-instant state, the 57 PKI_* / 43 PKI_REASON_* / 97 PKI_DIAG_* vocabularies, the 21 wrapper codes, the doctrine refusals and the deliberately unexposed engine surface. network is always 'none'. Never cached.

VALIDATION_ERROR

explain_code

explain

Any code this server can emit, explained from the registries vendored at the pkinative v1.0.0 tag (class, standard, CWE, remedy, the MCP input that lifts it); list: true enumerates one vocabulary.

NOT_FOUND

draft_governance_issue

governance

A local GitHub issue draft plus a compliance report for pkinative, pkinative-cli or pkinative-mcp — never submitted; a human files it under their own identity.

GOVERNANCE_VIOLATION, MISSING_OUTPUT_PATH, INVALID_EXTENSION, OUTPUT_EXISTS, SECURITY_VIOLATION

What every tool guarantees:

  • 🌐 No network, ever — no CRL / OCSP / AIA / TSA fetch, no telemetry, no GitHub, no URL from any argument, no operator-configurable endpoint. Revocation evidence and time-stamp tokens are inputs; create_ocsp_request and create_timestamp_request produce bytes you transport. Every tool advertises openWorldHint: false.

  • 🔑 No key byte leaves — signing keys are non-extractable Web Crypto handles; every key / PKCS#12 report is an allow-list view; key-bearing files (.key .p8 .p12 .pfx) are never written and never served as resources; convert_pem, decode_asn1 and encode_asn1 refuse private-key material. There is no key generation and no key export.

  • 🧭 Verdicts are reports, errors are refusals — a negative verdict is valid: false + reasons[] with the engine's PKI_REASON_* codes (43 of them); isError is reserved for an input the engine refuses (one of the 57 PKI_* codes, passed through verbatim with data.errorClass and a remedy) or a wrapper refusal (21 codes, each with a remedy). Branch on the code, never on the message.

  • 📐 Doctrine, surfaced not hidden — SHA-1 only with allowSha1: true; RSA needs an explicit rsaScheme ('pkcs1' | 'pss', no default); PBES2 and PBMAC1 only (a legacy RC2 / 3DES PKCS#12 is refused with the OpenSSL 3.4 conversion remedy); DSA and ML-DSA are parsed, never verified; diagnostics (97 PKI_DIAG_* codes) are not errors unless strict: true; the engine's 22 limits raise per call and never above the operator ceilings.

  • 🪙 Token-frugal — read and verdict tools accept verbosity: 'summary' and fields: [...]; artefacts arrive once as an embedded resource block or a resource_link, never duplicated into structuredContent.

  • 🔁 Reproducible on request — pin at, notBefore, signingTime, serialNumber and nonce (or let the operator pin the instant with PKINATIVE_MCP_AT) and an artefact or a verdict is a pure function of its inputs; structuredContent.sha256 is the proof (docs/guides/REPRODUCIBLE.md).

  • 🧪 OpenSSL 3 is the judge — every artefact the server writes is read back by OpenSSL 3.x in CI (23 conformant artefacts, 5 refused canaries, 12 command families), blocking on Linux and Windows.

All artefact-producing tools support two output modes and three encodings:

  • base64 (default) — the artefact is returned once as an embedded resource content block (PEM as text, DER as blob, with its MIME type: application/pkix-cert, application/pkcs10, application/pkcs7-signature, application/pkix-crl, application/ocsp-request, application/timestamp-query, …); structuredContent carries { mode, encoding, sizeBytes, sha256, summary, diagnostics } — with encoding: 'hex' the bytes land in structuredContent.hex instead (inline only, ≤ 64 KiB).

  • file — the artefact is written inside the sandbox configured by PKINATIVE_MCP_OUTPUT_DIR (exclusive create, never overwritten → OUTPUT_EXISTS; ≤ 1 GiB) and the result carries a resource_link. File I/O is disabled unless this variable is set; absolute paths, traversal, NUL bytes, a key-bearing or unknown extension and a symlink or junction whose real path leaves the sandbox are all rejected. The same sandbox serves every path input, so a create_csr → create_certificate → verify_certificate_chain chain never re-sends the bytes.

Token-frugal reads. The read and verdict tools (and describe_engine, explain_code) accept two optional inputs:

  • verbosity: 'summary' — a compact scalar-only verdict. E.g. verify_certificate_chain → { valid, reasonCodes, pathLength, evaluatedAt, revocationEvidence, diagnosticCount }; inspect_certificate → subject, issuer, serial, validity, key, extension kinds.

  • fields: ['a', 'b.c'] — projects the structured result to named dot-paths (≤ 16; arrays map over elements); composes after verbosity. Every path that matched nothing — nested paths included — is omitted and reported in _meta.unmatchedFields (with _meta.availableFields); a path holding a __proto__, constructor or prototype segment is VALIDATION_ERROR.

Smallest "can I trust this server certificate?" probe: { "leaf": {…}, "trustAnchors": [{…}], "at": "2027-01-01T00:00:00Z", "serverName": { "host": "example.test" }, "verbosity": "summary", "fields": ["valid", "reasonCodes"] } on verify_certificate_chain.

The server also ships seven MCP prompts — governance_contract, draft_issue_workflow, verify_before_trust, issue_certificate, sign_and_timestamp, offline_revocation, open_keys_safely — and exposes every non-secret file in the sandbox as a pkinative://output/{+path} resource.

Why pkinative?

pkinative-mcp inherits every guarantee of the underlying engine:

  • Zero runtime dependencies in the engine — pure TypeScript, no native bindings; Web Crypto for every signature, so no secret-dependent arithmetic runs in JavaScript (this server adds only the MCP SDK and zod: three runtime dependencies in total).

  • RFC 5280 path validation (building, §6.1 validation, name constraints, policies), RFC 6125 names, RFC 6960 OCSP, RFC 5652 CMS, RFC 3161 time-stamps, RFC 2986 CSRs, RFC 5958 / 8018 PKCS#8 with PBES2, RFC 7292 / 9579 PKCS#12 with PBMAC1, strict X.690 DER with opt-in BER.

  • 22 named, CWE-tagged limits on every parser loop — raised only by explicit input, never silently.

  • A frozen vocabulary — 57 PKI_* error codes, 43 PKI_REASON_* verdict reasons and 97 PKI_DIAG_* diagnostics, vendored at the v1.0.0 tag and passed through verbatim.

  • What it will NOT do: no key generation, no key export, no writer for PKCS#8 / PKCS#12 / CRL / OCSP response / time-stamp response, no DSA / ML-DSA verification, no network access, ever.


Related MCP server: verify-mcp

🚀 Installation

# Run directly with npx (recommended for MCP clients)
npx -y pkinative-mcp

# Or install globally
npm install -g pkinative-mcp
pkinative-mcp

Requirements: Node.js ≥ 22 (the engine's floor is ^22.22.2 || ^24.14.1 || >=25.8.2; describe_engine.capabilities.nodeFloorSatisfied tells you). From source: git clone https://github.com/Nizoka/pkinative-mcp.git && cd pkinative-mcp && npm ci --ignore-scripts && npm run build && node dist/cli.js.


⚙️ Configuration

Claude Desktop

Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "pkinative": {
      "command": "npx",
      "args": ["-y", "pkinative-mcp"],
      "env": {
        "PKINATIVE_MCP_OUTPUT_DIR": "/Users/you/Documents/mcp-pki"
      }
    }
  }
}

Claude Code

.mcp.json at the root of the project (the repository ships one that drives the built server from dist/cli.js):

{
  "mcpServers": {
    "pkinative": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "pkinative-mcp"],
      "env": { "PKINATIVE_MCP_OUTPUT_DIR": "${CLAUDE_PROJECT_DIR}/.mcp-pki" }
    }
  }
}

Cursor / Continue / Zed / Windsurf / Cline / Roo Code

Any MCP-compatible client that supports stdio servers will work. Use the same command + args + env triple. Example for Cursor (~/.cursor/mcp.json):

{
  "mcpServers": {
    "pkinative": {
      "command": "npx",
      "args": ["-y", "pkinative-mcp"],
      "env": { "PKINATIVE_MCP_OUTPUT_DIR": "/Users/you/Documents/mcp-pki" }
    }
  }
}

Windsurf / Cline / Roo Code use the same shape inside their respective MCP config files.

VS Code

.vscode/mcp.json:

{
  "servers": {
    "pkinative": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "pkinative-mcp"],
      "env": { "PKINATIVE_MCP_OUTPUT_DIR": "${workspaceFolder}/.mcp-pki" }
    }
  }
}

🌐 Supported AI Ecosystem & Clients

pkinative-mcp is designed for MCP-native environments and works with clients that support MCP over stdio or Streamable HTTP: Claude Desktop, Claude Code, Cursor, Continue, Zed, Windsurf, Cline, Roo Code, VS Code, ChatGPT (Streamable HTTP), the MCP Inspector, and any client built on the MCP TypeScript / Python SDKs. Because the server never opens a socket of its own, it is also safe to run inside air-gapped or egress-restricted hosts — which is where PKI material usually lives.

It is the third server of the "native" family, after pdfnative-mcp (PDF) and zipnative-mcp (ZIP): the same thin-wrapper architecture, the same sandbox, cache, projection and governance conventions, the same human-in-the-loop rules. The same engine is on the command line as pkinative-cli.

🔌 MCP protocol compliance

The server is built on the MCP TypeScript SDK v2 (@modelcontextprotocol/server) and speaks MCP 2026-07-28:

  • Stateless serving — server/discover replaces the session handshake; every result carries resultType and the _meta serverInfo envelope. Over HTTP, 2026-07-28 clients send Mcp-Method / Mcp-Name headers with each POST /mcp.

  • Cache hints — tools/list and prompts/list are public with a 24 h ttlMs, server/discover is public for 1 h, and resources/list / resources/templates/list / resources/read are private with ttlMs: 0 (sandbox files are per-host user data).

  • Protocol errors — tools/call with an unknown tool name is a JSON-RPC error (-32602, [UNKNOWN_TOOL] Unknown tool: …) rather than an isError result, and an unknown resource URI is -32602 (Invalid params), as the specification requires; isError: true is reserved for execution failures.

  • Automatic legacy fallback — a client that opens with initialize is served through the SDK's legacy path on both stdio and HTTP for every revision the SDK negotiates: 2024-10-07, 2024-11-05, 2025-03-26, 2025-06-18 and 2025-11-25 (legacy) next to 2026-07-28 (modern). Nothing changes for existing hosts.

  • HTTP — POST /mcp only; GET / DELETE /mcp answer 405 (no SSE resumability; the server is stateless). Bound to 127.0.0.1 with a Host / Origin guard (foreign values → 403); PKINATIVE_MCP_HTTP_TOKEN adds an opt-in bearer-token gate (401 + WWW-Authenticate without it); bodies above 256 MiB → 413.

  • stdio — the frame cap is 256 MiB so a multi-MiB base64 CMS message or PKCS#12 fits in one tools/call; as in every SDK release, a request sent before initialize is dropped without a reply on the legacy path.

  • Output schemas — every structuredContent validates against the tool's outputSchema (a 2026-07-28 MUST), including verbosity: 'summary' and fields projections: the read and verdict tools declare projectable schemas (all properties optional, additionalProperties: false kept). Input schemas carry no $schema keyword and no $ref by policy (some hosts forward inputSchema to function-calling APIs that reject unknown keywords). serverInfo carries websiteUrl; the resource template is pkinative://output/{+path}.

The tools/call payload (content, structuredContent, isError) is identical between the 2026-07-28 path and the legacy path; tests/http-modern.test.ts asserts it, and tests/schema-conformance.test.ts validates structuredContent with the SDK's JSON Schema 2020-12 validator.

Client

Transport

Protocol negotiated

Claude Desktop, Cursor, Continue, Zed, Windsurf, Cline

stdio

legacy initialize (2024-xx / 2025-xx) — unchanged

ChatGPT and other Streamable HTTP hosts

HTTP POST /mcp

legacy stateless streamable HTTP — unchanged

MCP 2026-07-28 clients (SDK v2 Client, current MCP Inspector)

stdio / HTTP

server/discover, cache hints, _meta envelope

Compatibility matrix — every negotiated revision, on both transports, with the test that proves it:

Revision

Era

stdio

HTTP POST /mcp

Proven by

2024-10-07

legacy initialize

✓

✓

tests/protocol-revisions.test.ts

2024-11-05

legacy initialize

✓

✓

tests/protocol-revisions.test.ts

2025-03-26

legacy initialize

✓

✓

tests/protocol-revisions.test.ts

2025-06-18

legacy initialize

✓

✓

tests/protocol-revisions.test.ts, tests/http-transport.test.ts

2025-11-25

legacy initialize

✓

✓

tests/protocol-revisions.test.ts, tests/cli-stdio.test.ts, tests/autonomy.test.ts

2026-07-28

modern (server/discover, _meta envelope, cache hints)

✓

✓ (+ bearer token)

tests/stdio-modern.test.ts, tests/http-modern.test.ts, tests/autonomy.test.ts

Environment variables

All nine are declared in server.json, read once at boot; an invalid value (a PKINATIVE_MCP_PORT outside 1–65535, a relative PKINATIVE_MCP_OUTPUT_DIR, a malformed instant or ceiling) refuses to start — never a silent fallback. Only the presence of a secret is logged.

Variable

Default

Meaning

PKINATIVE_MCP_OUTPUT_DIR

(unset)

Absolute path of the one sandbox directory: every path input (PKI objects, content, keys) is read from it and outputMode: 'file' outputs are written under it (exclusive create — never overwritten). The real path of every file read and of every parent written must stay inside it: a symlink or junction planted in the sandbox is SECURITY_VIOLATION on both sides. Key-bearing files (.key .p8 .p12 .pfx) are read by the key tools only, never written, never served as resources — and resources/* refuse key material by content too, whatever the name. Unset ⇒ every tool works on inline PEM / base64 only.

PKINATIVE_MCP_CACHE_DIR

(unset)

Opt-in content-addressed response cache (SHA-256 keyed, 1 h TTL, 256 MiB LRU, plaintext at rest; key namespaced by tool API, package and engine version). Never caches a path input, file output, a call carrying a password or a signer, a 'random' serial or nonce, a 'now' or unpinned instant, describe_engine or draft_governance_issue — nor any password-accepting tool while PKINATIVE_MCP_PASSWORD is set; a hit carries _meta.cached: true.

PKINATIVE_MCP_PORT

(unset → stdio)

When set to a valid port (1–65535), serves Streamable HTTP on http://127.0.0.1:<port>/mcp instead of stdio; any other value refuses to start. Request bodies up to 256 MiB are accepted (413 above). Loopback only, DNS-rebinding protection (foreign Host / Origin → 403), GET / DELETE → 405. No authentication unless PKINATIVE_MCP_HTTP_TOKEN is set — other local processes can reach the endpoint.

PKINATIVE_MCP_HTTP_TOKEN

(unset)

(secret) Opt-in bearer token for the HTTP transport (≥ 16 characters, no whitespace — a weaker value aborts startup). When set, every /mcp request must carry Authorization: Bearer <token>; otherwise 401 + WWW-Authenticate: Bearer realm="pkinative-mcp" (with error="invalid_token" only when credentials were sent — RFC 6750 §3.1). Compared constant-time, never logged.

PKINATIVE_MCP_PASSWORD

(unset)

(secret) The operator-side password of the encrypted PKCS#8 key or PKCS#12 file the tools open — it keeps the secret out of the model transcript. It counts as the password of every password-accepting tool (inspect_private_key, inspect_pkcs12, open_pkcs12, the signer of the signing tools): "describe without a password" needs it unset. Exclusive with the per-call password input (both at once is VALIDATION_ERROR). Never logged; only its presence is reported at startup.

PKINATIVE_MCP_AT

(unset → wall clock)

Pinned instant (ISO 8601, or epoch milliseconds) every omitted at / notBefore / signingTime falls back to, for reproducible verdicts and artefacts. Unset: the wall clock — and a clock-dependent result is never cached.

PKINATIVE_MCP_MAX_INPUT_BYTES

67108864 (64 MiB)

Operator ceiling for limits.maxInputBytes and the decoded size of one PKI object (the engine default). Integer ≥ 1024. A per-call value above it is LIMIT_CEILING_EXCEEDED; a ceiling below the engine default tightens the default silently.

PKINATIVE_MCP_MAX_KDF_ITERATIONS

10000000

Operator ceiling for limits.maxKdfIterations and limits.maxPkcs12KdfIterations (the engine default): the CPU bound of a hostile PBES2 key or PKCS#12 file. Integer ≥ 1.

PKINATIVE_MCP_MAX_PATHS_EXPLORED

1000

Operator ceiling for limits.maxPathsExplored (the engine default): the RFC 5280 §6 path-building work bound. Integer ≥ 1.

The operator ceilings. The engine's 22 named PkiLimits (maxInputBytes, maxDepth, maxNodes, maxIntegerBytes, maxOidBytes, maxBerSegments, maxPemBlocks, maxExtensions, maxGeneralNames, maxNameAttributes, maxPolicies, maxChainLength, maxPolicyNodes, maxRevokedCertificates, maxOcspSingleResponses, maxPathsExplored, maxSignerInfos, maxAttributes, maxCmsCertificatesAndCrls, maxKdfIterations, maxPkcs12Bags, maxPkcs12KdfIterations) can be raised per call through the limits fragment for trusted input only. Three of them are bounded by the three operator ceilings above: a value above the ceiling is refused with LIMIT_CEILING_EXCEEDED and data.env names the variable an operator would raise. There is no "unbounded" and no ceiling a tool argument can move. describe_engine reports the defaults, the ceilings and every cap in force.


🛠 Tool reference

Every PKI object input is an object with exactly one of pem (RFC 7468 text), derBase64 (the DER as base64, exactly once — no data: URI, not PEM) or path (relative, inside the sandbox; .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). Every engine-touching tool takes the PARSE fragment (encodingRules, pemMode, strict, limits); the read and verdict tools take verbosity and fields; the artefact tools take outputMode, outputPath and encoding. The fragments are described in Shared input fragments. Error results: content[0].text = "<tool> failed [CODE]: message", isError: true, _meta.error = { code, message, data? }. Full input / output schemas are advertised by tools/list; the consumer contract — catalogue, decision tree, recipes and the complete error table — is docs/AGENT_CONTRACT.md. Every example below is an executable call from examples/ or a tool's _meta.examples (the fixtures/… paths are the TEST-ONLY PKI the examples runner copies into the sandbox; test-only-password is their published password).

convert_pem

The RFC 7468 layer (catalogue). Inputs: mode (decode | encode), input (PKI_INPUT; encode takes exactly one DER value), label, extractIndex, PARSE, PROJECTION, OUTPUT (encoding der / pem / hex). Output: blocks[] (index, label, sizeBytes, offset, sha256), count, extracted, and the artefact when a block is extracted or DER is wrapped. Codes: SECURITY_VIOLATION (a private-key block, or key material anywhere in the input — a key behind a prefix or among other blocks included — under any label), NOT_FOUND (an index past the last block), VALIDATION_ERROR, PKI_PEM_UNEXPECTED_LABEL, PKI_PEM_*.

{ "mode": "decode", "input": { "path": "fixtures/leaf.crt.pem" }, "label": "CERTIFICATE", "extractIndex": 0, "encoding": "hex", "fields": ["extracted", "artefact"] }

resolve_oid

Object identifiers in one call. Inputs: mode (name | validate | list | encode | decode), oids[] (≤ 1000), filter, offset / limit (200 default, 2000 max), form (content | tlv), relative, hex or input (a PKI_INPUT — pem / derBase64 / path — as the decode source), PARSE, PROJECTION. Output: per mode — oids[] with names or validity, entries[] + total / hasMore, or one oid + name. encode answers hex per OID, never an artefact: a PEM-wrapped OID is encode_asn1 { spec: { type: 'oid', value }, encoding: 'pem', label }. Codes: VALIDATION_ERROR (a member outside its mode, relative without form: 'tlv', both hex and input), PKI_OID_INVALID (encode / decode; validate reports it instead), PKI_ASN1_*.

{ "mode": "name", "oids": ["1.2.840.10045.4.3.2", "2.5.29.17", "1.3.6.1.5.5.7.3.1", "1.2.3.4"] }

compute_fingerprint

The digest of one object. Inputs: input (PKI_INPUT, any label or DER; a bundle is refused), mode (fingerprint | key-id | shake256), algorithm, webcrypto, outputLength (≤ 1 MiB), separator, letterCase, PARSE, PROJECTION. Output: hex, formatted, algorithm, sizeBytes, label, from (where the public key came from in key-id). Codes: VALIDATION_ERROR (an algorithm outside its mode, SHA-384 / 512 for a key identifier), PKI_X509_* / PKI_ASN1_* (key-id on an object without a public key).

{ "input": { "path": "fixtures/leaf.csr.pem" }, "mode": "key-id", "fields": ["formatted", "from"] }

decode_asn1

The X.690 node tree. Inputs: input (PKI_INPUT — never a private key or a PKCS#12), sequence, nodePath ('0.2.1'), read (boolean | integer | small-integer | enumerated | null | bit-string | octet-string | oid | relative-oid | string | time), stringType, timeType, reencode, label (the PEM label of a reencode artefact when the input had none or a sub-node is selected), allowTrailingData, maxPreviewBytes (default 32), PARSE, PROJECTION, OUTPUT. Output: nodes[] (path, offset, lengths, tag, constructed, children, preview, oid + oidName), nodeCount, truncated (past 20 000 nodes), value (the typed read), reencode.identical, artefact. Codes: SECURITY_VIOLATION, NOT_FOUND (a missing nodePath step, naming the range), PKI_ASN1_UNEXPECTED_TAG (a wrong reader), PKI_API_MISUSE (an IMPLICIT string / time without stringType / timeType), PKI_ASN1_*, ENGINE_ERROR (the call stack exhausted inside a raised limits.maxDepth — the message names the remedy: lower it).

{ "input": { "path": "fixtures/leaf.crt.der" }, "nodePath": "0.1", "read": "integer", "verbosity": "summary" }

encode_asn1

A JSON node spec to DER. Inputs: spec (one node { type, … }), label (for encoding: 'pem'), PARSE, OUTPUT (encoding der / hex / pem; default der). Output: the artefact (sizeBytes, sha256, hex or the resource) and summary (root tag, node count). Codes: VALIDATION_ERROR naming the JSON path ($.children[1].value …), SECURITY_VIOLATION (a private-key label, or a value that parses as a key or a PKCS#12), PKI_LIMIT_EXCEEDED (nesting past limits.maxDepth), PKI_ASN1_*, PKI_OID_INVALID.

{ "spec": { "type": "sequence", "children": [{ "type": "oid", "value": "1.2.840.10045.4.3.2" }] }, "encoding": "hex" }

inspect_certificate

The RFC 5280 report. Inputs: certificate (PKI_INPUT), extension (one of the 19 kinds), rawExtensions, issuer (PKI_INPUT) or checkSelfSignature, requireAlgorithmMatch, fingerprints[], allowSha1, PARSE, PROJECTION. Output: version, serialNumber, signatureAlgorithm, issuer / subject (+ issuerString / subjectString), validity, subjectPublicKeyInfo, extensions[], selfIssued, fingerprints, signature (valid / algorithm, when checked), diagnostics[]. Codes: NOT_FOUND (an extension the certificate lacks), PKI_PEM_UNEXPECTED_LABEL (a key given as a certificate — refused by label before any byte is parsed), PKI_X509_*, PKI_CRYPTO_ALGORITHM_REFUSED (SHA-1 without allowSha1).

{ "certificate": { "path": "fixtures/leaf.crt.pem" }, "issuer": { "path": "fixtures/inter.crt.pem" }, "fingerprints": ["SHA-256", "SHA-1"], "fields": ["signature", "extensions.kind", "subjectString", "fingerprints"] }

create_certificate

Issue a certificate. Inputs: spec (serialNumber — integer, hex string or 'random', subject, issuer, notBefore, notAfter / validityDays, extensions — basicConstraints, keyUsage, extendedKeyUsage, subjectAltName, issuerAltName, SKI / AKI, raw), issuerCertificate (PKI_INPUT; omitted = self-signed), subjectPublicKey (a certificate, a CSR or a PUBLIC KEY), signer (SIGNER), allowSha1, PARSE, OUTPUT (default pem). Output: the artefact and summary (serialNumber, randomSerial, subject, issuer, selfSigned, signatureAlgorithm, notBefore / notAfter with notBeforeSource, the extension kinds, signatureVerified, publicKeyFrom). Codes: KEY_MISMATCH (the new certificate did not verify against the issuer or itself), RSA_SCHEME_REQUIRED, KEY_TYPE_REQUIRED, SECRET_REQUIRED, UNSUPPORTED_KEY, VALIDATION_ERROR (an unknown spec member, SHA-1 without allowSha1, notAfter before notBefore).

{
  "spec": {
    "serialNumber": 1,
    "subject": { "C": "FR", "O": "Acme", "CN": "Acme Test CA" },
    "notBefore": "2027-01-01T00:00:00Z",
    "validityDays": 3650,
    "extensions": { "basicConstraints": { "ca": true, "pathLen": 0 }, "keyUsage": ["keyCertSign", "cRLSign"] }
  },
  "signer": { "key": { "path": "fixtures/ed25519.key.pem" } }
}

encode_x509_structure

One building block to DER. Inputs: structure (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), spec (a closed object), signer (signature-algorithm only — the key is imported non-extractable and only its algorithm is read), label, allowSha1, PARSE, OUTPUT (default hex). Output: the artefact (hex by default) and summary. Codes: VALIDATION_ERROR naming spec <member> (an unknown or missing member is never dropped silently), KEY_TYPE_REQUIRED, SECRET_REQUIRED, RSA_SCHEME_REQUIRED.

{ "structure": "subject-alt-name", "spec": { "dns": ["svc.example.test", "*.wild.example.test"], "ip": ["192.0.2.1"] } }

decode_extension

One extnValue by OID. Inputs: oid, valueHex or value (PKI_INPUT — the DER inside the OCTET STRING), critical, PARSE, PROJECTION. Output: kind, oid, name, critical, sizeBytes, decoded (the kind's members), diagnostics[] (the criticality check). Codes: PKI_X509_EXTENSION_MALFORMED (remedy: decode_asn1), VALIDATION_ERROR.

{ "oid": "2.5.29.19", "valueHex": "30030101ff", "critical": true }

check_certificate_name

The RFC 6125 identity verdict. Inputs: certificate form — certificate (PKI_INPUT) + host or ip, chain[] (PKI_BUNDLE, for the CN fallback), allowCommonNameFallback (default false), allowWildcards (default true); match form — presented + reference; PARSE, PROJECTION. Output: form, valid, reasons[], reference, subject / presented, match. Codes: PKI_REASON_NAME_MISMATCH and friends in reasons[] (a verdict, never isError); VALIDATION_ERROR (both forms at once).

{ "certificate": { "path": "fixtures/leaf.crt.pem" }, "host": "a.wild.example.test", "verbosity": "summary" }

check_certificate_purpose

The RFC 5280 §4.2.1.12 purpose verdict. Inputs: certificate, chain[] (leaf's issuer first), purpose (serverAuth | clientAuth | codeSigning | emailProtection | timeStamping | ocspSigning | any | a dotted OID), restrictIssuers (default true), requireExplicitPurpose (default false), PARSE, PROJECTION. Output: valid, reasons[], purposeOid, purposeName, subject, pathLength. Codes: PKI_REASON_PURPOSE_NOT_PERMITTED (with the path[i] element) in reasons[]; VALIDATION_ERROR (an unknown purpose name).

{ "certificate": { "path": "fixtures/leaf.crt.pem" }, "purpose": "codeSigning" }

inspect_csr

The RFC 2986 report. Inputs: request (PKI_INPUT), rawExtensions, verify (default true — the proof of possession, one Web Crypto verification), allowSha1, PARSE, PROJECTION. Output: subject (+ subjectString), subjectPublicKeyInfo, attributes[], extensions[], challengePassword (present or not, never its text), signatureAlgorithm, signature (valid, reasons[]). Codes: PKI_REASON_SIGNATURE_NOT_CHECKED in signature.reasons[] (SHA-1 without allowSha1), PKI_X509_*, PKI_PEM_UNEXPECTED_LABEL.

{ "request": { "path": "fixtures/leaf.csr.pem" }, "verbosity": "summary" }

create_csr

A PKCS#10 request. Inputs: spec (subject, extensions — subjectAltName, keyUsage, extendedKeyUsage, basicConstraints, subjectKeyIdentifier, raw; the issuer's extensions are refused), subjectPublicKey (required for an encrypted PKCS#8 key), signer (SIGNER), allowSha1, PARSE, OUTPUT (default pem). Output: the artefact and summary (subject, signatureAlgorithm, the requested extension kinds, signatureVerified, publicKeyFrom). Codes: KEY_MISMATCH (the key does not match the public key named), RSA_SCHEME_REQUIRED, SECRET_REQUIRED, KEY_TYPE_REQUIRED, VALIDATION_ERROR.

{
  "spec": {
    "subject": { "C": "FR", "O": "Acme", "CN": "svc.example.test" },
    "extensions": { "subjectAltName": { "dns": ["svc.example.test", "www.svc.example.test"] }, "keyUsage": ["digitalSignature"], "extendedKeyUsage": ["serverAuth"] }
  },
  "signer": { "key": { "path": "fixtures/leaf.key.pem" } }
}

verify_certificate_chain

The RFC 5280 §6 verdict. Inputs: leaf (PKI_INPUT), TRUST (trustAnchors[], candidates[], crls[], ocspResponses[], at, requireRevocation, purposes[], policy), serverName (host | ip), ocspNonce, requireOcspNonce, allowSha1, PARSE, PROJECTION. Output: valid, reasons[], path[] (leaf first up to the anchor), pathLength, explored, signatureVerifications[], evaluatedAt + evaluatedAtSource, revocationEvidence. Codes: PKI_REASON_* in reasons[] — NO_TRUST_ANCHOR, EXPIRED, NOT_YET_VALID, REVOKED, REVOCATION_UNKNOWN, REVOCATION_STALE, NAME_MISMATCH, PURPOSE_NOT_PERMITTED, … (a verdict, never isError); VALIDATION_ERROR, PKI_* for inputs the engine refuses.

{
  "leaf": { "path": "fixtures/revoked.crt.pem" },
  "candidates": [{ "path": "fixtures/inter.crt.pem" }],
  "trustAnchors": [{ "path": "fixtures/root.crt.pem" }],
  "crls": [{ "path": "fixtures/inter.crl.pem" }],
  "ocspResponses": [{ "path": "fixtures/revoked.ocsp.der" }],
  "requireRevocation": true,
  "at": "2027-01-01T00:00:00Z",
  "fields": ["valid", "reasons.code", "revocationEvidence", "pathLength"]
}

build_certificate_path

Path discovery, signatures not checked. Inputs: leaf, candidates[], trustAnchors[], at, purposes[], policy, PARSE, PROJECTION. Output: found, structuralReasons[], path[], pathLength, explored, valid (always false), reasons[], signaturesChecked: false. Codes: PKI_REASON_SIGNATURE_NOT_CHECKED (always present), PKI_REASON_NO_TRUST_ANCHOR (in structuralReasons[] when no path reached an anchor).

{ "leaf": { "path": "fixtures/leaf.crt.pem" }, "candidates": [{ "path": "fixtures/inter.crt.pem" }], "trustAnchors": [{ "path": "fixtures/root.crt.pem" }], "at": "2027-01-01T00:00:00Z", "verbosity": "summary" }

validate_certificate_path

RFC 5280 §6.1 on an ordered path. Inputs: path[] (PKI_BUNDLE, leaf first), trustAnchors[], at, checkSignatures (default true), policy, allowSha1, PARSE, PROJECTION. Output: valid, reasons[], failingReasons[], path[], signaturesChecked, signatures[] (one verdict per link), evaluatedAt. Codes: PKI_REASON_SIGNATURE_INVALID, PKI_REASON_SIGNATURE_NOT_CHECKED (checkSignatures: false), VALIDATION_ERROR.

{ "path": [{ "path": "fixtures/leaf.crt.pem" }, { "path": "fixtures/inter.crt.pem" }], "trustAnchors": [{ "path": "fixtures/root.crt.pem" }], "at": "2027-01-01T00:00:00Z" }

inspect_crl

The RFC 5280 §5 report. Inputs: crl (PKI_INPUT), find (serialHex | certificate), issuer (PKI_INPUT, one signature check), allowSha1, PARSE, PROJECTION. Output: issuer (+ issuerString), signatureAlgorithm, thisUpdate / nextUpdate, crlNumber, isDelta, baseCrlNumber, issuingDistributionPoint, entryCount, extensions[], entry (revoked, revocationDate, reason, invalidityDate), signature. Codes: VALIDATION_ERROR, PKI_X509_*, PKI_CRYPTO_ALGORITHM_REFUSED.

{ "crl": { "path": "fixtures/inter.crl.der" }, "find": { "certificate": { "path": "fixtures/revoked.crt.pem" } }, "fields": ["entry", "issuerString", "crlNumber", "nextUpdate.iso"] }

check_crl_revocation

One certificate against one CRL. Inputs: certificate, crl, delta, issuer (the CRL issuer — without it the list is not accepted as evidence: valid: false with PKI_REASON_REVOCATION_UNKNOWN, a verdict), at, staleTolerance (ms), allowSha1, PARSE, PROJECTION. Output: valid, reasons[], signatureVerified, deltaSignatureVerified, crlNumber, isDelta, deltaCrlNumber, evaluatedAt. A delta whose signature does not verify against issuer is not applied: valid: false with PKI_REASON_REVOCATION_UNKNOWN on delta. Codes: PKI_REASON_REVOKED (with the reason), PKI_REASON_REVOCATION_UNKNOWN and the freshness / scope reasons in reasons[]; VALIDATION_ERROR (a delta without a deltaCRLIndicator).

{ "certificate": { "path": "fixtures/ed25519.crt.pem" }, "crl": { "path": "fixtures/ed25519.crl.der" }, "delta": { "path": "fixtures/ed25519-delta.crl.der" }, "issuer": { "path": "fixtures/ed25519.crt.pem" }, "at": "2027-01-01T00:00:00Z" }

create_ocsp_request

The RFC 6960 request you POST yourself. Inputs: certificate, issuer, hashAlgorithm (SHA-1 default | SHA-256), nonce ('random' or 1–32 bytes of hex), certIdOnly, label (the PEM label for encoding: 'pem'), PARSE, OUTPUT (default der, .orq). Output: the artefact and summary (certIdHex, nonceHex, randomNonce, serial). Codes: VALIDATION_ERROR.

{ "certificate": { "path": "fixtures/leaf.crt.pem" }, "issuer": { "path": "fixtures/inter.crt.pem" }, "hashAlgorithm": "SHA-256", "nonce": "random", "encoding": "hex" }

inspect_ocsp_response

The RFC 6960 report. Inputs: response (PKI_INPUT), responder (PKI_INPUT, one signature check; omitted = the embedded certificates are tried), PARSE, PROJECTION. Output: status, basicResponse (responder id, producedAt, responses[] with CertID / status / updates, extensions, certificates, signature algorithm), noncePresent, signature (valid, source). Codes: PKI_ASN1_*, PKI_X509_*, VALIDATION_ERROR.

{ "response": { "path": "fixtures/revoked.ocsp.der" }, "responder": { "path": "fixtures/ocsp.crt.pem" }, "fields": ["signature", "basicResponse.responses.status", "basicResponse.responses.certId.serialNumber", "noncePresent"] }

check_ocsp_status

One response for one certificate. Inputs: response, certificate, issuer, responder, responderTrusted, hashAlgorithm, nonce (hex), requireNonce, at, staleTolerance, futureTolerance, allowSha1, PARSE, PROJECTION. Output: valid, reasons[], status (good | revoked | unknown | null), signatureVerified, responderAuthorized, responder, responderSource, hashAlgorithm, certIdHex, evaluatedAt. Codes: PKI_REASON_REVOKED, the freshness, nonce and authorisation reasons in reasons[] (a non-successful response status is a negative verdict with status: null); VALIDATION_ERROR.

{ "response": { "path": "fixtures/leaf.ocsp.der" }, "certificate": { "path": "fixtures/leaf.crt.pem" }, "issuer": { "path": "fixtures/inter.crt.pem" }, "nonce": "1538349c433373ed058c291909df5b3e", "requireNonce": true, "at": "2027-01-01T00:00:00Z", "verbosity": "summary" }

sign_cms

An RFC 5652 SignedData. Inputs: content (CONTENT_INPUT) or contentDigest (HEX, detached only), detached, contentType (OID), certificate, chain[], crls[], sid (issuerAndSerialNumber default | subjectKeyIdentifier), signingTime (an instant, 'now', 'none'), signingCertificateV2, algorithmProtection, signedAttributes[] / unsignedAttributes[] (DER Attributes as hex), signer (SIGNER), allowSha1, PARSE, OUTPUT (default der, .p7s). Output: the artefact and summary (detached, contentType, signerSubject / signerSerial, sid, digest and signature algorithms, signingTime with signingTimeSource, certificatesEmbedded, crlsEmbedded, attribute counts, signatureVerified). Codes: KEY_MISMATCH (the new signature did not verify against the signer certificate), RSA_SCHEME_REQUIRED, SECRET_REQUIRED, KEY_TYPE_REQUIRED, VALIDATION_ERROR (contentDigest without detached, certificate missing with signer.key), SECURITY_VIOLATION (a content.path naming a key-bearing extension or holding key material — the content a tool reads from the sandbox is never a key), PKI_API_MISUSE (a repeated or builder-owned attribute type), ENGINE_ERROR.

{
  "content": { "path": "fixtures/content.txt" },
  "detached": true,
  "certificate": { "path": "fixtures/leaf.crt.pem" },
  "chain": [{ "path": "fixtures/inter.crt.pem" }],
  "signingTime": "2027-01-01T00:00:00Z",
  "signer": { "key": { "path": "fixtures/leaf.key.pem" } }
}

verify_cms

The RFC 5652 verdict. Inputs: signedData (PKI_INPUT), content (CONTENT_INPUT) or contentDigest (detached signatures), TRUST (trustAnchors[], candidates[], crls[], ocspResponses[], at, requireRevocation, purposes[]), atTimeStamp, requireSigningCertificate, requireAlgorithmProtection, allowTrailingData, allowSha1, PARSE, PROJECTION. Output: valid, reasons[], signers[] (per signer: valid, intact, reasons[], chain, time-stamps, revocation), signerCount, signatureVerifications[], evaluatedAt, atTimeStamp. Codes: PKI_REASON_* in reasons[] (NO_TRUST_ANCHOR, SIGNATURE_INVALID, REVOKED, TSP_*, …); PKI_API_MISUSE (content given for an attached message — it carries its own), VALIDATION_ERROR (both content and contentDigest), PKI_ASN1_TRAILING_DATA (the zero padding of a PDF /Contents without allowTrailingData).

{ "signedData": { "path": "fixtures/attached.p7s" }, "trustAnchors": [{ "path": "fixtures/root.crt.pem" }], "crls": [{ "path": "fixtures/inter.crl.pem" }], "ocspResponses": [{ "path": "fixtures/leaf.ocsp.der" }], "requireRevocation": true, "purposes": ["emailProtection"], "at": "2027-01-01T00:00:00Z" }

inspect_cms

The RFC 5652 report. Inputs: signedData, allowTrailingData, signerCertificate (one key check), signerIndex, content (for a signer without signed attributes), allowSha1, PARSE, PROJECTION. Output: version, contentType, contentLength (null = detached), digestAlgorithms[], certificates[], crlCount, ocspResponseCount, signerInfos[] (sid, algorithms, signing time as claimed, attributes, time-stamp count, signature as hex), signerSignature. Codes: NOT_FOUND (a signerIndex past the last signer, when signerCertificate is given — the index names the signer that check applies to), PKI_ASN1_TRAILING_DATA, PKI_*.

{ "signedData": { "path": "fixtures/detached.p7s" }, "signerCertificate": { "path": "fixtures/leaf.crt.pem" }, "content": { "path": "fixtures/content.txt" }, "fields": ["signerInfos.signature", "signerInfos.sid", "signerSignature"] }

amend_cms

The one legitimate edit. Inputs: signedData, signerIndex, operation (add-unsigned-attribute | add-timestamp), attribute (HEX), token (PKI_INPUT — a .tst or a whole .tsr), allowSha1, PARSE, OUTPUT (default der). Output: the artefact and summary. Codes: VALIDATION_ERROR with data.detail PKI_REASON_TSP_IMPRINT_MISMATCH (a token over anything but the signer's signature value — the remedy names the inspect_cms → create_timestamp_request → amend_cms sequence), NOT_FOUND, PKI_API_MISUSE (a signed-only attribute type), PKI_*.

{ "signedData": { "path": "fixtures/attached.p7s" }, "operation": "add-unsigned-attribute", "attribute": "300d06032a0304310606042a030405" }

create_timestamp_request

The RFC 3161 request you POST yourself. Inputs: data (CONTENT_INPUT) or digestHex (HEX), hashAlgorithm (SHA-256 default | SHA-384 | SHA-512), nonce ('random', decimal or 0x hex), policy (OID), certReq (default true), label (the PEM label for encoding: 'pem'), PARSE, OUTPUT (default der, .tsq). Output: the artefact and summary (imprintHex, hashAlgorithm, nonce, randomNonce, policy, certReq). Codes: VALIDATION_ERROR (a digest whose length does not match the algorithm), SECURITY_VIOLATION (a data.path naming a key-bearing extension or holding key material).

{ "data": { "path": "fixtures/content.txt" }, "nonce": "random" }

inspect_timestamp

The RFC 3161 report. Inputs: input (PKI_INPUT), as (response | token | tstinfo), PARSE, PROJECTION. Output: as, status (statusStrings, failInfo), token (CMS envelope: signer count, certificates), tstInfo (policy, messageImprint, serialNumber, genTime, accuracy, ordering, nonce, tsa, extensions). Codes: PKI_* (the bytes are none of the three structures), PKI_STRICT_DIAGNOSTIC (with strict: true — OpenSSL tokens carry PKI_DIAG_CMS_SET_NOT_SORTED).

{ "input": { "path": "fixtures/content.tst" }, "as": "token", "fields": ["token.tstInfo.genTime.iso", "token.tstInfo.messageImprint", "token.certificates.subject"] }

verify_timestamp

The RFC 3161 verdict. Inputs: token or response, request (the .tsq you sent — imprint, nonce and policy all checked), data (CONTENT_INPUT) or digestHex, TRUST (trustAnchors[], candidates[], crls[], ocspResponses[], at, requireRevocation), allowNonCriticalTimeStampingEku, allowSha1, PARSE, PROJECTION. Output: valid, reasons[], genTime, earliest / latest (accuracy applied), tsa, chain ({ valid, path[], reasons[] }), signatureVerifications[], evaluatedAt. Codes: PKI_REASON_TSP_IMPRINT_MISMATCH, PKI_REASON_TSP_NOT_GRANTED, PKI_REASON_TSP_REQUEST_MISMATCH, PKI_REASON_TSP_TOKEN_INVALID and the chain reasons in reasons[]; VALIDATION_ERROR (nothing stamped was given — a token verified without what it stamps proves nothing).

{ "response": { "path": "fixtures/content.tsr" }, "request": { "path": "fixtures/content.tsq" }, "data": { "path": "fixtures/content.txt" }, "trustAnchors": [{ "path": "fixtures/root.crt.pem" }], "candidates": [{ "path": "fixtures/inter.crt.pem" }], "at": "2027-01-01T00:00:00Z" }

inspect_private_key

A PKCS#8 key described, no key byte. Inputs: key (PKI_INPUT), tryImport, password (or PKINATIVE_MCP_PASSWORD), keyType (ec-p256 | ec-p384 | ec-p521 | ed25519 | ed448 | rsa | rsa-pss), hash, rsaScheme, saltLength, allowSha1, PARSE, PROJECTION. Output: format (pkcs8 | encrypted-pkcs8), kind, curve, algorithm + algorithmName, attributes, publicKey (embedded or not), encryption (scheme, PBKDF2 iterations, PRF, AES key size, salt length), encryptedBytes, imported (handle type, algorithm, usages). Codes: SECRET_REQUIRED, KEY_TYPE_REQUIRED, RSA_SCHEME_REQUIRED, PKI_CRYPTO_DECRYPTION_FAILED (a wrong password), PKI_KEY_ENCRYPTION_UNSUPPORTED (a legacy cipher, with the openssl pkcs8 remedy), PKI_* (a PUBLIC KEY or a non-PKCS#8 DER).

{ "key": { "path": "fixtures/leaf.key.enc.pem" }, "tryImport": true, "password": "test-only-password", "keyType": "ec-p256", "fields": ["imported", "encryption.scheme"] }

inspect_pkcs12

A PKCS#12 described, no key byte. Inputs: pkcs12 (PKI_INPUT; BER with encodingRules: 'ber' for a Windows-written PFX), password (optional), PARSE, PROJECTION. Output: version, mac (kind pbmac1 | pkcs12-kdf | null, parameters), contents[] (path, encryption scheme and parameters, bags of the plain ones), bagCount, macVerified (with a password; false is a report only when no SafeContents is encrypted — otherwise a wrong password is PKI_CRYPTO_DECRYPTION_FAILED before the MAC is reported), opened[] (allow-list views: certBags by subject / issuer / serial, key bags by algorithm, curve and encryption parameters, crlBags by size). A set PKINATIVE_MCP_PASSWORD counts as the password: describing a file without one needs the variable unset. Codes: PKI_CRYPTO_DECRYPTION_FAILED (a wrong password on an encrypted SafeContents), PKI_KEY_MAC_UNSUPPORTED, PKI_KEY_ENCRYPTION_UNSUPPORTED (each with the OpenSSL 3.4 conversion remedy).

{ "pkcs12": { "path": "fixtures/legacy.p12" }, "fields": ["mac.kind", "contents.path", "contents.encryption.scheme"] }

open_pkcs12

A PKCS#12 opened in one call. Inputs: pkcs12, password (or PKINATIVE_MCP_PASSWORD), allowUnverifiedIntegrity, rsaScheme (pkcs1 | pss), hash, exportCertificates, PARSE, PROJECTION, OUTPUT (pem only — a bundle has no DER form). Output: valid, integrity (verified | unverified | mismatch), reasons[], keys[] (algorithm, handle type, usages — never a byte), certificates[], keyCount / certificateCount / crlCount, exportSkipped, artefact (the PEM bundle). Codes: SECRET_REQUIRED (no password from either channel); in reasons[]: PKI_REASON_PKCS12_MAC_MISMATCH (a wrong password or an altered file — nothing is decrypted), PKI_REASON_PKCS12_ENCRYPTION_UNSUPPORTED, PKI_REASON_PKCS12_INTEGRITY_UNVERIFIED, PKI_REASON_PKCS12_RSA_SCHEME_UNSPECIFIED; MISSING_OUTPUT_PATH, OUTPUT_TOO_LARGE.

{ "pkcs12": { "path": "fixtures/leaf.p12" }, "password": "test-only-password", "exportCertificates": true }

describe_engine

The offline preflight. Inputs: PROJECTION only. Output: server (name, version, toolApiVersion, protocol, toolCount), engine (name, version, nodeFloor), capabilities (canVerify, canSign, canDecrypt, node, nodeFloorSatisfied, platform), randomness, defaultLimits, ceilings, caps, sandbox (enabled, cacheEnabled, pinnedInstant, the extension allow-lists), network: 'none', registries (the 57 / 43 / 97 codes, the wrapper codes, purposes, key-usage bits, the OID registry count), refusals (the doctrine), unexposed. Codes: VALIDATION_ERROR.

{ "fields": ["defaultLimits", "ceilings", "sandbox", "capabilities"] }

explain_code

Any code, explained. Inputs: code, or list: true with kind (error | reason | diagnostic | wrapper), PROJECTION. Output: code, kind, summary, standard, severity, errorClass, engineClass, cwe, since, remedy — or codes[] + count. Codes: NOT_FOUND (an unknown code).

{ "code": "PKI_REASON_PKCS12_RSA_SCHEME_UNSPECIFIED" }

draft_governance_issue

Draft a governance-compliant GitHub issue locally for a human to review and submit — for pkinative (engine behaviour: parsing, verification, limits, refusals), pkinative-cli or pkinative-mcp (wrapper behaviour: schemas, sandbox, transport). The server never contacts GitHub — it has no network code path at all; it returns the draft Markdown plus a machine-readable compliance report. Present both to the user, then STOP: they review and submit under their own identity. Inputs: title, summary, issueType (bug | feature | security | docs | performance), targetRepo, reproduction ({ command, result }), expectedBehavior, actualBehavior, affectedPackages[], duplicateSearchPerformed (must be true), outputMode (inline | file) + outputPath (a relative .md in the sandbox). Output: draftMarkdown, warnings[], compliance (the nine keys of .github/AGENT_RULES.md). Codes: GOVERNANCE_VIOLATION (a runtime dependency proposed, an embedded private key or password, no reproduction, duplicateSearchPerformed: false), MISSING_OUTPUT_PATH, INVALID_EXTENSION, OUTPUT_EXISTS, SECURITY_VIOLATION.

{
  "title": "parseCertificateList refuses a delta CRL with an empty revokedCertificates list",
  "summary": "A delta CRL (deltaCRLIndicator present) whose revokedCertificates SEQUENCE is absent is refused with PKI_X509_STRUCTURE_INVALID although RFC 5280 makes the field OPTIONAL.",
  "issueType": "bug",
  "targetRepo": "pkinative",
  "reproduction": { "command": "inspect_crl { crl: { path: \"fixtures/empty-delta.crl\" } }", "result": "PKI_X509_STRUCTURE_INVALID at revokedCertificates" },
  "expectedBehavior": "The delta CRL parses with entryCount 0.",
  "affectedPackages": ["pkinative", "pkinative-mcp"],
  "duplicateSearchPerformed": true
}

See the governance_contract and draft_issue_workflow prompts and docs/guides/AI_GOVERNANCE.md for the full human-in-the-loop contract.


🧩 Shared input fragments

The same ten fragments appear across the catalogue; a tool's description names the ones it carries. Every object schema is closed (additionalProperties: false, Zod .strict() at every depth): an unknown or misspelt key is VALIDATION_ERROR naming the path, never silently dropped.

Fragment

Shape

Rules

PKI_INPUT

{ pem } | { derBase64 } | { path }

Exactly one. pem is RFC 7468 text starting with -----BEGIN (a PEM text holding several blocks where one object is expected is refused — use convert_pem extractIndex or a bundle); derBase64 is the DER as base64 exactly once (no data: URI, not PEM); path is relative to the sandbox, PEM or DER sniffed by its first bytes, extension allow-listed, never cached. The label is checked before any byte is parsed (PKI_PEM_UNEXPECTED_LABEL).

PKI_BUNDLE

PKI_INPUT[] (1–256 items)

trustAnchors, candidates, crls, ocspResponses, chain, path: every PEM block of every item is taken, in order.

CONTENT_INPUT

{ text } | { base64 } | { path }

The bytes to sign, verify or time-stamp: UTF-8 text, base64 (≤ 128 MiB decoded) or a sandbox file (any extension but a key-bearing one, ≤ 1 GiB — it is content, not a PKI object; a file holding key material is SECURITY_VIOLATION, since content a tool reads is never a key).

HEX

string

contentDigest, digestHex, valueHex, attribute, nonce, ocspNonce, serialHex: hexadecimal; an 0x prefix, colons and spaces are tolerated (^(?:0x)?(?:[0-9a-fA-F]{2}[:\s]?)+$).

PARSE

encodingRules, pemMode, strict, limits

encodingRules 'der' (strict X.690, default) or 'ber' (indefinite / non-minimal lengths, as legacy CMS producers write); pemMode 'strict' (RFC 7468 exactly) or 'lax' (whitespace, line length, RFC 1421 headers); strict: true escalates the first engine warning to PKI_STRICT_DIAGNOSTIC; limits below.

LIMITS

{ maxInputBytes, maxDepth, … } (the 22 keys)

Raise the engine's named bounds for this call, trusted input only; absent keys keep the engine defaults; every value is an integer ≥ 1; maxInputBytes, maxKdfIterations / maxPkcs12KdfIterations and maxPathsExplored are bounded by the operator ceilings (LIMIT_CEILING_EXCEEDED); a call stack exhausted inside a raised maxDepth is a coded ENGINE_ERROR naming the remedy (lower it).

SIGNER

{ key } | { p12 }, + password, keyType, hash, rsaScheme, saltLength

Exactly one of key (a PKCS#8 private key, plain or PBES2-encrypted, PEM or DER — SEC1 / PKCS#1 keys must be converted with openssl pkcs8 -topk8 first) or p12 (PBES2 + PBMAC1 only). The key is imported into a non-extractable Web Crypto key and never leaves the engine. The algorithm follows the key: ECDSA on the key's curve (SHA-256 / 384 / 512 by curve), Ed25519 / Ed448 (no hash), RSA requires rsaScheme ('pkcs1' | 'pss', optional saltLength); an encrypted key needs password (or PKINATIVE_MCP_PASSWORD) and keyType unless a certificate or public key input names it. A call carrying a signer is never cached.

TRUST

trustAnchors, candidates, crls, ocspResponses, at, requireRevocation, purposes, policy

A verdict is relative to the anchors you name (at least one). at is an ISO 8601 date-time (UTC unless zoned), epoch milliseconds, or 'now'; omitted = PKINATIVE_MCP_AT, else the wall clock (never cached). requireRevocation: true demands fresh evidence for the end-entity certificate (PKI_REASON_REVOCATION_UNKNOWN otherwise — the engine's rule; an intermediate is judged from the evidence you pass, its status reported in revocationEvidence). purposes (≤ 16) are EKU names or OIDs; policy holds the RFC 5280 §6.1.1 inputs (initialPolicySet, requireExplicitPolicy, inhibitPolicyMapping, inhibitAnyPolicy).

PROJECTION

verbosity, fields

'full' (default) or 'summary'; up to 16 dot-paths kept after verbosity (arrays map over elements); every path that matched nothing, nested ones included, is reported in _meta.unmatchedFields with _meta.availableFields; a __proto__ / constructor / prototype segment is VALIDATION_ERROR.

OUTPUT

outputMode, outputPath, encoding

'base64' (default, ≤ 50 MiB inline) or 'file' (a relative outputPath whose extension matches the encoding — .pem for pem; .der .crt .cer .csr .p10 .crl .p7s .p7m .p7b .cms .orq .tsq .bin .asn1 … for der; key-bearing extensions refused; never overwritten; ≤ 1 GiB). encoding 'pem', 'der' or 'hex' (inline only, ≤ 64 KiB; the hex lands in structuredContent.hex).

Every result also carries diagnostics[] (de-duplicated by code and path, capped at 200 with diagnosticsTruncated) and diagnosticCounts; the password input, when a tool has one, is a plain string (≤ 4096 characters) that is never echoed, logged or cached.


🔐 Security model

pkinative-mcp runs inside the host process and exposes a stdio MCP server (or a loopback-only HTTP endpoint). It treats every certificate, request, list, response, message, token, key and PKCS#12 as untrusted input and performs no I/O outside the configured sandbox other than the opt-in response cache under PKINATIVE_MCP_CACHE_DIR.

  • Secrets: two channels, never a third. A password reaches the server through the per-call password input or the operator-side PKINATIVE_MCP_PASSWORD — one source per call (both at once is VALIDATION_ERROR); SECRET_REQUIRED when neither is present. A password is never echoed in a result or an error text, never logged (only the presence of the variable is reported at boot), never written to a cache file, never served as a resource. Private keys are imported into non-extractable Web Crypto handles; every key and PKCS#12 report is an allow-list view built field by field (src/key-views.ts), and the test suite proves with a needle table computed from the fixtures that no key byte, scalar, JWK member, encrypted-key body or password is printed by any result, on stderr, in a cache file or on the stdio transcript (tests/secret-leak.test.ts).

  • The sandbox (PKINATIVE_MCP_OUTPUT_DIR) is the one filesystem boundary for reads (every path input, resources/read) and writes (outputPath, governance drafts). Paths must be relative; absolute / drive / UNC paths, .., NUL bytes and non-allow-listed extensions are rejected; the real path of the file read and of the parent written must stay inside (a symlink or junction planted in the sandbox is SECURITY_VIOLATION on both sides); files are opened exclusively (OUTPUT_EXISTS — never overwritten); a failed write removes the partial file. Key-bearing extensions (.key .p8 .p12 .pfx) are read by the key tools only, never written, never listed nor served by resources/* — and resources/list / resources/read refuse key material by content as well, whatever the file name: a PEM key after a prefix, a key hidden among many blocks, a raw DER key behind junk or base64 key material inside a text file is SECURITY_VIOLATION. The same content test guards the generic encoders (convert_pem encode takes exactly one DER value and refuses key material) and the content inputs (content.path of sign_cms, data.path of create_timestamp_request): what a tool reads from the sandbox as content is never a key. Unset ⇒ inline PEM / base64 only.

  • Caps — every byte and count bound is a constant in src/caps.ts and advertised by describe_engine.caps:

Cap

Value

stdio frame / HTTP body

256 MiB

decoded inline PKI inputs per call / inline content

128 MiB / 128 MiB

content.path read fully into memory

1 GiB

inline output (an artefact, a resource read)

50 MiB

file output

1 GiB

inline hex artefact

64 KiB

bundle items / DER attributes inline / OIDs per call

256 / 256 / 1000

resolve_oid list page

200 default / 2000 max

SHAKE256 output / password / fields paths / decode_asn1 preview / tree nodes

1 MiB / 4096 / 16 / 32 bytes / 20 000

diagnostics per result

200 (de-duplicated by code and path)

resources/list entries / walk depth / resources/read

1000 / 8 / 50 MiB

response cache TTL / size

3600 s / 256 MiB

engine defaults (limits)

the 22 PkiLimits of pkinative 1.0.0 — maxInputBytes 64 MiB, maxKdfIterations 10 000 000, maxPathsExplored 1000, … (describe_engine.defaultLimits)

  • The cache gate (isCacheable, src/server.ts) — with PKINATIVE_MCP_CACHE_DIR set, a result is served from the cache only when nothing about it depends on the environment, a secret, the clock or randomness: never a path input (a path is a reference, not content), never outputMode: 'file', never a call carrying a password or a signer, never a 'random' serial or nonce, never a 'now' or unpinned instant, never describe_engine or draft_governance_issue, and never any password-accepting tool while PKINATIVE_MCP_PASSWORD is set (the operator password may have been used without any secret in the input). The key namespace carries the tool API, package and engine versions, so an engine upgrade never serves bytes the previous engine wrote.

  • Doctrine refusals (describe_engine.refusals) — the lines the server will not cross, each with its remedy: no network, no key generation (bring keys from your HSM, KMS or openssl genpkey), no key export, no writer for PKCS#8 / PKCS#12 / CRL / OCSP response / time-stamp response (use the issuing software or OpenSSL and verify the result here), SHA-1 refused by default, no default RSA scheme, legacy PKCS#12 and PBES1 keys refused (convert once with OpenSSL 3.4+: openssl pkcs12 -legacy … then -export -pbmac1_pbkdf2), DSA / ML-DSA parsed but never verified or signed, diagnostics are not errors, limits raise only explicitly and under the ceilings, randomness lives in the wrapper and is reported (randomSerial, randomNonce).

  • Inputs are validated against strict JSON Schemas + Zod at the boundary of every tool — unknown or misspelt keys at any depth are VALIDATION_ERROR; base64 is sanity-checked (data: URIs, PEM where DER is expected, double-encoded or empty payloads refused with a remedy) before any parser runs; specs are built prototype-pollution-safe (tests/prototype-pollution.test.ts).

  • HTTP transport (PKINATIVE_MCP_PORT) binds loopback only with a Host / Origin guard; it has no authentication unless PKINATIVE_MCP_HTTP_TOKEN is set (then 401 without a valid bearer token).

Network & egress: none

None, ever. The server has no network code path: no CRL, OCSP, AIA or TSA fetch, no telemetry, no GitHub, no update check, no URL from any tool argument, and — unlike its PDF sibling — no operator-configurable endpoint of any kind. describe_engine reports network: 'none' and every tool advertises openWorldHint: false. Revocation evidence and time-stamp tokens are inputs you fetched out of band; create_ocsp_request and create_timestamp_request produce the bytes you transport (application/ocsp-request, application/timestamp-query). The engine itself never opens a socket, never touches the filesystem and never evals.

See SECURITY.md for the responsible disclosure process and the threat model, and docs/guides/SECURITY.md for the defences with their CWE ids.


🧪 Conformance (OpenSSL 3)

OpenSSL 3.x is the external judge of this server — the role veraPDF plays for pdfnative-mcp: what the tools write, a foreign implementation must read and verify. npm run corpus:pki drives the built server (scripts/generate-pki-corpus.ts, corpus table in scripts/lib/pki-corpus.ts) under a pinned instant to write the interoperability corpus into test-output/pki/ with a manifest.json (status, SHA-256, the second digest of every deterministic entry): 23 conformant artefacts — self-signed CAs and leaves for every signature family (Ed25519, RSA PKCS#1 v1.5, RSA-PSS, ECDSA P-256), a pathLen-0 intermediate and a leaf under it, three CSRs, attached and detached CMS messages (the Ed25519 signer included), two time-stamp requests, two OCSP requests, PEM re-encodings of the DER fixtures — and 5 refused canaries that must be refused with their declared code (a SHA-1 signature, a legacy PKCS#12 signer, an RSA key without a scheme, a notAfter before notBefore, a URL member in a spec). A canary that writes bytes is an XPASS and fails the run.

npm run validate:pki (scripts/validate-pki.ts) then checks every artefact at two levels:

  • Level 0 — engine round trip, in-process through dist/server.js: every certificate inspected and verified to the corpus CA at the pinned instant (verify_certificate_chain with serverName and purposes for the leaves), every request read with its proof of possession, every CMS message verified with the anchors (and the detached content), every OCSP and time-stamp request decoded and recomputed independently with node:crypto (CertID hashes, serial, message imprint, nonce), and every deterministic entry produced twice hashing the same. It needs no external tool and always runs.

  • Level 1 — OpenSSL, openssl 3.x on PATH (OPENSSL_BIN overrides; LibreSSL and OpenSSL < 3.0 are refused with the reason): 12 command families — x509 -text over every certificate (DER and PEM), verify to each corpus CA (-purpose sslserver for the leaves, -untrusted for the intermediate, -attime the pinned instant), req -verify, crl -text, cms -verify (attached and detached, -CAfile the fixture root), asn1parse, ocsp -req_text, ts -query -text — with the raw transcripts under test-output/pki/reports/. Skipped with the reason when OpenSSL is absent; a failure under PKINATIVE_INTEROP_REQUIRED=1 or the gate's --require-all.

A coverage canary holds the corpus to declared.conformant / declared.refused in docs/data/interop.json, so a silent regression cannot shrink the validated set. Per file the validator reports PASS / FAIL / XFAIL / XPASS / INFRA / SKIP; exit codes: 0 every expectation met, 1 a FAIL / XPASS / canary, 2 the corpus or dist/ missing, 3 OpenSSL required but absent. The vitest form of the same level-1 checks is tests/interop/openssl.test.ts (npm run test:interop), and tests/interop/lint.test.ts (npm run test:lint-certs, publish profile) runs the zlint and pkilint certificate linters over the corpus with the reviewed waivers of scripts/data/lint-waivers.json (none today).

The gate is blocking: the ci workflow runs it with --require-all on Linux (Node 22 and 24), Windows and macOS, and the interop workflow repeats it on four OS legs (ubuntu-22.04, ubuntu-24.04, windows-latest, macos-latest), two of which — interop (ubuntu-24.04) and interop (windows-latest) — are required status checks. OpenSSL is a runner tool, never a dependency. A PASS is validation evidence against a specific OpenSSL revision (3.5.5 on the development host), not a certification. See docs/guides/CONFORMANCE.md.

Autonomy proof

tests/autonomy.test.ts replays one scripted agent scenario (S0–S18: the handshake → the catalogue → describe_engine → inspect_certificate and the cache hit over the wire → the chain verdicts with CRL and OCSP evidence → name and purpose → a CA, a CSR and an issued leaf in file mode → a CMS signature signed, verified and then tampered → a time-stamp request built and a response verified → a PKCS#12 opened without a key byte in the transcript → resources/* → hostile inputs refused with a code and a remedy → the ceilings → protocol errors → draft_governance_issue → prompts/get → explain_code → a clean shutdown) against the built dist/cli.js on four transport × era pairs — stdio legacy, stdio modern, HTTP legacy, HTTP modern with PKINATIVE_MCP_HTTP_TOKEN — under a sandbox holding the fixtures, a cache directory and a lowered PKINATIVE_MCP_MAX_PATHS_EXPLORED ceiling (every instant pinned per call — the scenario asserts pinnedInstant: null), asserting no interaction, no unexpected isError, a code and a remedy on every refusal, a cache hit over the wire, no secret needle anywhere (stdout, stderr, the cache, the sandbox), a JSON-only stdout and a clean exit. To replay it by hand from Claude Code, Claude Desktop or the MCP Inspector, see docs/guides/LOCAL_TESTING.md (the repository ships a ready .mcp.json).


🧪 Local development

git clone https://github.com/Nizoka/pkinative-mcp.git
cd pkinative-mcp
npm ci --ignore-scripts   # .npmrc sets ignore-scripts=true: nothing builds on install
npm run gate:fast         # before every commit: typecheck:all, lint, test, code-tables, server-json, verify:docs
npm run gate              # the CI profile: + build, dist checks, stdio smoke test, tool shape, samples, coverage, docs, the OpenSSL corpus and interop
npx tsx scripts/gate.ts --publish --require-all   # release branches: everything incl. lint:certs (zlint / pkilint); a skipped step fails

npm run gate (scripts/gate.ts) is the single definition of green — 2381 tests across 111 files, the coverage thresholds of vitest.config.ts (94 / 90 / 95 / 94), and the built server driven over stdio, where stdout must carry JSON-RPC frames only and dist/ must hold no byte of a test key. It prints one line per step and writes the logs to test-output/.gate/<step>.log; --only <step> runs one step, --from <step> resumes, --json gives machine output (call the script directly to pass flags: PowerShell swallows a bare -- after npm run). The individual steps are ordinary npm scripts:

Script

What it does

npm run typecheck:all

tsc --noEmit over src/ + tests/, then scripts/

npm run lint

eslint src --max-warnings 0 — a warning fails

npm test / npm run test:coverage

vitest (dot reporter) / with v8 coverage — thresholds in vitest.config.ts

npm run build

tsc -p tsconfig.build.json → dist/

npm run examples:check

runs the 89 examples/*.json live through the tools/call handler (multi-step examples chained)

npm run test:generate / npm run verify:samples

the 98-sample baseline: the built server under the hermetic env → test-output/samples/, held to tests/_fixtures/samples.sha256.json (10 byte-exact, 3 by their TBS structure, 85 semantic)

npm run corpus:pki / npm run validate:pki

the OpenSSL corpus (23 + 5) written to test-output/pki/ and validated at levels 0 and 1

npm run test:interop / npm run test:lint-certs

the vitest form of level 1 (PKINATIVE_INTEROP_REQUIRED=1 fails instead of skipping) / the zlint + pkilint linters

npm run verify:tool-shape

the built tools/list structure against tests/_fixtures/tool-shape.json, under the 448 KiB catalogue and 8 KiB instructions budgets (npx tsx scripts/tool-shape.ts --write after a deliberate schema change only)

npm run verify:docs

every count, version, tool, code, operator variable, link and anchor in the docs held to docs/assets/ecosystem.json and the source tree

npm run agents:rules

regenerates .claude/rules/*.md from .github/instructions/*.instructions.md

npm run release:prepare

the mechanical version bump (never commits, tags or publishes)

npm run hooks:install / hooks:uninstall

opt-in git hooks (pre-commit: lint + a CRLF guard; pre-push: the fast gate)

Every script, its flags and its exit codes are listed in scripts/README.md. The 42 fixtures under tests/fixtures/pki/ were made by OpenSSL 3.5.5 (tests/fixtures/PROVENANCE.md), are pinned by SHA256SUMS, and are never regenerated in place: every private key there is public TEST-ONLY material (password test-only-password).

OpenSSL on your machine. Level 1 needs openssl 3.x on PATH (or OPENSSL_BIN). Linux: the distribution package. macOS: brew install openssl@3 (the system /usr/bin/openssl is LibreSSL and is refused with the reason). Windows: an OpenSSL 3 build on PATH, or the one that ships with Git for Windows (usr/bin/openssl.exe); point OPENSSL_BIN at it when it is not first on PATH. Without it, validate:pki and interop SKIP with the reason — and fail under --require-all.

Smoke-test the server over stdio:

node dist/cli.js
# In another terminal, send a JSON-RPC initialize request via stdin (e.g. with the MCP Inspector).
npx @modelcontextprotocol/inspector --cli node dist/cli.js --method tools/list

Run it over HTTP with a bearer token and a sandbox:

PKINATIVE_MCP_PORT=3000 PKINATIVE_MCP_HTTP_TOKEN=change-me-to-a-real-secret PKINATIVE_MCP_OUTPUT_DIR=/tmp/pki npx pkinative-mcp
# POST http://127.0.0.1:3000/mcp with Authorization: Bearer …

Contributors: start at AGENTS.md (the repository rules shared by every coding agent and contributor) and see docs/guides/LOCAL_TESTING.md for the full local-verification workflow — the quality gate, examples-as-tests, the sample baseline, the OpenSSL corpus, and driving the built server from Claude Code or the MCP Inspector.

📣 Release process

pkinative-mcp follows the same release formalism as pkinative, pdfnative-mcp and zipnative-mcp:

  • One release note file per tag in release-notes/vX.Y.Z.md

  • CHANGELOG.md mirrors each release bullet list

  • GitHub Release body is copied from release-notes/vX.Y.Z.md

  • npx tsx scripts/release-prepare.ts --version X.Y.Z applies the mechanical part of a version bump (it never commits, tags or publishes); the version moves in lock-step across package.json, src/version.ts, server.json and CITATION.cff (tests/metadata.test.ts) and in docs/assets/ecosystem.json (npm run verify:docs, rule manifest-shape)

  • A release branch must pass npx tsx scripts/gate.ts --publish --require-all — every step, OpenSSL included, with no skip (the certificate linters run when zlint and pkilint are installed)

  • npm publication is handled by GitHub Actions Trusted Publishing (OIDC), without NPM_TOKEN, from the protected npm-publish environment: the workflow checks that the tag equals the package version, runs the publish gate and publishes with --provenance; a second job attests the tarball (build provenance) together with a CycloneDX SBOM and attaches both to the GitHub Release

  • Every Linux and Windows job starts with step-security/harden-runner (the action does not support macOS; the macOS job is the one documented exemption), checks out with persist-credentials: false, installs with npm ci --ignore-scripts and runs under least-privilege permissions; actions are pinned by commit SHA; the OpenSSL interoperability gate is blocking

  • Human-in-the-loop: coding agents prepare and verify; the maintainer pushes, opens the pull request, tags and publishes (.github/AGENT_RULES.md)

See release-notes/TEMPLATE.md for the canonical structure and publication checklist, and CONTRIBUTING.md for the release procedure.


📚 Project structure

src/
├── cli.ts                      # entrypoint: stdio (default, 256 MiB frames) or Streamable HTTP (PKINATIVE_MCP_PORT); reads the operator knobs once
├── http.ts                     # Node http <-> Web Request/Response bridge, 256 MiB body cap, Host/Origin loopback guard
├── auth.ts                     # opt-in HTTP bearer token (PKINATIVE_MCP_HTTP_TOKEN)
├── server.ts                   # Server factory, dispatch by tool kind, cache hints, the cache gate, SERVER_INSTRUCTIONS, 7 prompts, resources
├── caps.ts                     # every byte / count cap + the three operator ceilings
├── limits.ts                   # the 22 PkiLimits as the `limits` fragment; LIMIT_CEILING_EXCEEDED
├── sandbox.ts                  # the single filesystem boundary (PKINATIVE_MCP_OUTPUT_DIR): extension allow-lists, realpath checks, wx writes
├── pki-input.ts                # pem XOR derBase64 XOR path; PEM sniffing, label discipline, bundles
├── content-input.ts            # text XOR base64 XOR path for the bytes to sign / verify / time-stamp; the HEX grammar
├── parse-context.ts            # the PARSE fragment (encodingRules, pemMode, strict, limits) and the per-call input budget
├── signer.ts                   # PKCS#8 / PKCS#12 -> non-extractable SigningKey; the algorithm rules (rsaScheme, hashes)
├── secrets.ts                  # the two password channels (input XOR PKINATIVE_MCP_PASSWORD), redaction
├── key-views.ts                # allow-list views of keys and PKCS#12 bags (never a key byte)
├── trust.ts                    # the TRUST fragment (anchors, candidates, evidence, at, purposes, policy) and reasons[]
├── time.ts                     # ISO 8601 instants, PKINATIVE_MCP_AT, clock-dependence
├── random.ts                   # serialNumber:'random' / nonce:'random' (node:crypto), reported in summaries
├── x509-spec.ts, asn1-spec.ts  # the closed JSON specs of create_certificate / create_csr / encode_* (depth-bounded, pollution-safe)
├── names.ts, spki.ts           # host / IP identity inputs; public-key kinds and curves
├── pki-errors.ts               # PkiError -> ToolError (the 57 PKI_* codes verbatim, classes, remedies), guard()
├── wrapper-codes.ts            # the 21 wrapper codes with meaning and remedy (served by explain_code)
├── diagnostics.ts              # engine diagnostic sink (de-duplicated, capped at 200) -> diagnostics[]; strict escalation
├── projection.ts               # verbosity / fields projection for the read and verdict tools
├── output.ts                   # artefact emitter: embedded resource (pem / der / hex) or exclusive sandbox file
├── resources.ts                # pkinative://output/{+path} over the sandbox (key-bearing files never listed nor served)
├── cache.ts                    # opt-in SHA-256 response cache (PKINATIVE_MCP_CACHE_DIR)
├── views.ts, wire.ts, base64.ts  # wire views (no raw DER echoes), hex helpers, base64 boundary diagnostics
├── engine.ts                   # engine version and Node floor, the Web Crypto capability probe
├── governance.ts               # AI-governance / HITL contract text + draft validation
├── errors.ts, version.ts, index.ts
├── data/pkinative/             # the registries vendored at the pkinative v1.0.0 tag: errors, reasons, diagnostics, limits, core-exports + generated code-tables.ts
└── tools/                      # one file per tool (34), _definition.ts (kinds, annotations), _registry.ts + _group-*.ts (catalogue order), _shared.ts
scripts/                        # TypeScript run by tsx — the full table is in scripts/README.md
├── gate.ts                     # THE quality gate (npm run gate / gate:fast; --publish --require-all on release branches)
├── generate-samples.ts         # npm run test:generate — the examples through the built server -> test-output/samples/
├── verify-samples.ts           # npm run verify:samples — the 98-sample baseline (tests/_fixtures/samples.sha256.json)
├── generate-pki-corpus.ts      # npm run corpus:pki — the OpenSSL interoperability corpus (23 + 5)
├── validate-pki.ts             # npm run validate:pki — level 0 (engine round trip) + level 1 (OpenSSL 3.x); exit 0 / 1 / 2 / 3
├── tool-shape.ts               # structural tools/list fingerprint and size budgets (--write refreshes the fixture)
├── verify-docs.ts              # npm run verify:docs — the docs held to docs/assets/ecosystem.json and the source tree
├── release-prepare.ts          # mechanical version bump (never commits, tags or publishes)
├── build-claude-rules.ts       # npm run agents:rules — .github/instructions/ -> .claude/rules/
├── build-code-tables.mjs       # regenerates src/data/pkinative/code-tables.ts from the vendored registries
├── verify-issue.mjs            # governance draft checker (npm run verify:issue)
├── lib/                        # pki-corpus, interop-tools (OpenSSL), sample-fingerprint, tool-shape, mcp-surface, markdown-anchors, agent-config, prose-language, json-schema-lite
└── helpers/, data/, validators/  # hermetic env, I/O, the lint waivers, the pkilint driver
docs/
├── AGENT_CONTRACT.md           # the consumer contract for agents that USE the server (catalogue, decision tree, recipes, error reference)
├── AI_GUIDE.md, KNOWLEDGE_BASE.md, API_STABILITY.md
├── guides/                     # AI_GOVERNANCE, LOCAL_TESTING, SECURITY, SIGNING, VERIFICATION, REVOCATION, TIMESTAMPS, KEYS, CONFORMANCE, REPRODUCIBLE
├── data/                       # errors, reasons, diagnostics, limits, core-exports (mirrors of src/data/pkinative/), interop.json (the corpus counts)
└── assets/ecosystem.json       # the single source of every count and version quoted in the docs
examples/                       # 89 executable tools/call sequences (npm run examples:check)
AGENTS.md, CLAUDE.md            # repository rules for coding agents; .github/instructions/ holds the per-area rules
.claude/                        # shared agent settings, the fail-closed guard hook, generated rules, the release-audit skill
.github/workflows/              # ci (Linux Node 22 / 24, Windows, macOS — the gate with --require-all), interop (4 OS legs, blocking),
                                #   sample-regression, publish (Trusted Publishing + SBOM + attestation), docs, codeql, scorecard,
                                #   dependency-review, audit, node-current
.github/rulesets/               # branch and tag rulesets to import
tests/                          # vitest suites (one per tool / module), _fixtures/ (tool shape, sample baseline, the registry schema),
                                #   fixtures/pki/ (42 OpenSSL-made TEST-ONLY files, SHA256SUMS, PROVENANCE.md), interop/ (OpenSSL, linters), tools/ (repository tooling)

🗺 Roadmap

v1.0.0 is the first release, on pkinative 1.0.0: the whole runtime surface of the engine (117 / 117 runtime exports reached by a named tool), offline. The full plan — the released milestone, what is blocked upstream, next steps and the long-term direction — lives in ROADMAP.md.

Blocked upstream, by design (the engine does not have it, and a thin wrapper will not fake it):

  • Key generation — pkinative generates nothing; keys come from your HSM, KMS or openssl genpkey.

  • Writers for PKCS#8, PKCS#12, CRL, OCSP response and time-stamp response — the engine has none; issue them with your CA software or OpenSSL and verify them here.

  • DSA and ML-DSA verification — parsed, reported, never verified or signed (PKI_REASON_SIGNATURE_NOT_CHECKED / UNSUPPORTED_KEY).

Have a feature idea? Open an issue or PR — or let an agent draft one with draft_governance_issue and submit it yourself.


⭐ Star the project

If pkinative-mcp is useful to you, please ⭐ this repository — and consider also starring the underlying engine Nizoka/pkinative. Stars help others discover the project and motivate continued development.


🤝 Contributing

Contributions are very welcome. Please read CONTRIBUTING.md, check the open issues, and follow the code of conduct.


📄 License

MIT © 2026 Nizoka. Third-party material: THIRD-PARTY-NOTICES.md.

pkinative-mcp is built on top of pkinative and the Model Context Protocol TypeScript SDK.

Available Tools

34 tools
amend_cmsAmend CMS (add an unsigned attribute or an RFC 3161 counter time-stamp)A
Idempotent

The one edit a signed message legitimately receives afterwards, every signed octet unchanged: operation 'add-unsigned-attribute' appends attribute (one DER Attribute as hex) to signer signerIndex's unsigned attributes; 'add-timestamp' appends token — a TimeStampToken, or the whole TimeStampResp a TSA returned (unwrapped; a declined response is refused) — as id-aa-signatureTimeStampToken (CAdES-T / PAdES B-T). A counter time-stamp stamps the signer's SIGNATURE VALUE, not the document: the token's imprint is checked against that value BEFORE anything is written and a token over anything else is refused with VALIDATION_ERROR (data.detail PKI_REASON_TSP_IMPRINT_MISMATCH, with the remedy: inspect_cms fields:['signerInfos.signature'] → hash → create_timestamp_request digestHex → POST to your TSA yourself → amend_cms). Trust is not judged here (verify_cms is). Deterministic for the same inputs. Refuses: NOT_FOUND for a signerIndex past the last signer, PKI_API_MISUSE for a signed-only attribute type, PKI_* for unreadable bytes. Output DER (default), PEM (CMS) or hex, inline or as a sandbox file (never overwritten).

NameRequiredDescriptionDefault
tokenNoThe time-stamp for add-timestamp: a TimeStampToken (.tst) or the whole TimeStampResp (.tsr, unwrapped to its token; a declined response is refused): exactly one of pem / derBase64 / path.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
encodingNoArtefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'der'.der
allowSha1NoAccept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write.
attributeNoOne DER Attribute (SEQUENCE { type, SET OF values }) for add-unsigned-attribute. A type RFC 5652 allows only signed is refused (PKI_API_MISUSE). Hexadecimal (an 0x prefix, colons and spaces are tolerated).
operationYes'add-unsigned-attribute' writes `attribute` verbatim; 'add-timestamp' writes `token` as id-aa-signatureTimeStampToken after checking its imprint.
outputModeNo'base64' (default) returns the artefact inline as an embedded resource (≤ 50 MiB). 'file' writes it inside PKINATIVE_MCP_OUTPUT_DIR (never overwrites; ≤ 1 GiB) and returns a resource_link.base64
outputPathNoRelative path inside the sandbox (only when outputMode='file'); the extension must match the encoding (.p7s .p7m .p7b .cms .der for der; .pem for pem). Key-bearing extensions (.key .p8 .p12 .pfx) are refused: nothing here writes a private key.
signedDataYesThe CMS SignedData to amend (CMS / PKCS7 PEM, or DER: .p7s .p7m): exactly one of pem / derBase64 / path.
signerIndexNoWhich signer receives the attribute (encoded order; NOT_FOUND past the last).
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der

Output Schema

NameRequiredDescription
hexNoThe artefact as lowercase hex (when encoding='hex').
modeYes
sha256YesSHA-256 of the DER bytes (lowercase hex) — the reproducibility proof.
summaryYes
encodingYes
filePathNoSandboxed absolute path (when outputMode='file').
sizeBytesYesSize of the DER artefact.
diagnosticsYesNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
diagnosticCountsYesOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare non-read-only, non-destructive, idempotent; the description adds the real behavior: imprint is verified against the signer's signature value BEFORE anything is written, token over anything else is refused with VALIDATION_ERROR and PKI_REASON_TSP_IMPRINT_MISMATCH, declined TimeStampResp is refused, deterministic for the same inputs, and refusal codes (NOT_FOUND, PKI_API_MISUSE, PKI_*) are named.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the purpose and the two operations, and every sentence carries information (error codes, remedy path, refusal list). It is long and em-dash-dense, packing the imprint-mismatch remedy into one clause, which costs some readability but not correctness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with nested inputs, 13 parameters, an output schema and several refusal modes, the description covers what is preserved (signed octets unchanged), what is validated before writing, which errors surface, what the outputs look like, and where trust is decided instead. Nothing an agent needs in order to call this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, so the schema carries the parameter detail; the description goes beyond it by explaining the interaction semantics — that `token` is either a TimeStampToken or a whole unwrapped TimeStampResp, that `attribute` is one DER Attribute written verbatim, and that output is DER/PEM/hex inline or as a never-overwritten sandbox file. It does not add anything about `limits`, `strict`, `allowSha1`, or `encodingRules`, hence a 4 rather than 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names the exact verb and resource (amend a signed CMS message) and states the only two edit kinds: appending an unsigned attribute or a counter time-stamp. It explicitly separates this from verify_cms ('trust is not judged here') and from create_timestamp_request, so an agent can place it among siblings without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Per-operation guidance is explicit: 'add-unsigned-attribute' appends `attribute` to `signerIndex`; 'add-timestamp' appends `token` as id-aa-signatureTimeStampToken (CAdES-T / PAdES B-T). It also gives the exact remediation chain when the imprint does not match (inspect_cms → hash → create_timestamp_request → POST to TSA → amend_cms), which is an unusual level of routing detail.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

build_certificate_pathBuild certificate path (discovery to a trust anchor, signatures NOT checked)A
Read-onlyIdempotent

Path discovery only (RFC 5280 §6 path building): walks from leaf through the untrusted candidates to one of YOUR trustAnchors under the structural rules evaluated at at (validity, basic constraints, key usage, name constraints, the policy inputs; purposes steer discovery but the leaf's extended key usage is judged by verify_certificate_chain, not here) and reports the path it found, leaf first up to the anchor. NO signature is verified: the engine marks every link PKI_REASON_SIGNATURE_NOT_CHECKED, so valid is always false and the verdict of this tool is found (a path reached an anchor, structuralReasons empty); the leaf alone with PKI_REASON_NO_TRUST_ANCHOR when none did. Use it to learn which intermediates a chain needs, then validate_certificate_path (ordered path, signatures) or verify_certificate_chain (the full verdict with revocation). Never isError for a negative result. Pin at for a reproducible, cacheable result. Token-frugal: verbosity:'summary' (found, structuralReasonCodes, pathLength).

NameRequiredDescriptionDefault
atNoThe instant the verdict is evaluated at. An ISO 8601 date-time (UTC unless zoned; e.g. 2027-01-01T00:00:00Z), epoch milliseconds as digits, or 'now'. Omitted: PKINATIVE_MCP_AT when the operator pinned it, else the wall clock (a clock-dependent result is never cached).
leafYesThe end-entity certificate to start from: exactly one of pem / derBase64 / path.
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
policyNoRFC 5280 §6.1.1 policy inputs.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
purposesNoExtended key usages the leaf must allow: serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, ocspSigning, any, or a dotted OID.
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
candidatesNoUntrusted intermediate certificates the path may use (every PEM block of every item is taken). Optional; the certificates embedded in a CMS message are also candidates.
trustAnchorsYesTrust anchors (root certificates you trust) (every PEM block of every item is taken). At least one is required: a verdict is relative to the anchors you name.
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der

Output Schema

NameRequiredDescription
pathNoThe path found, leaf first, up to (and including) the trust anchor; the leaf alone (with the reason) when no anchor was reached.
foundNoA path from the leaf to one of the trust anchors was found and passes the structural rules (structuralReasons empty). This is the verdict of THIS tool; the signatures are not checked.
validNoThe engine verdict — always false here, because no signature was verified (PKI_REASON_SIGNATURE_NOT_CHECKED on every link). Read `found`.
reasonsNoWhy the verdict is negative (empty when valid): the engine PKI_REASON_* codes, each with the standard it cites and the path of the offending element. A reason is returned, never thrown.
exploredNoCandidate paths explored (bounded by limits.maxPathsExplored).
pathLengthNo
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
evaluatedAtNoThe ISO instant used — re-pass it as `at` to reproduce the result.
reasonCodesNo
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
evaluatedAtSourceNo
signaturesCheckedNoAlways false: run validate_certificate_path or verify_certificate_chain for the signatures.
structuralReasonsNoreasons[] without the PKI_REASON_SIGNATURE_NOT_CHECKED markers: what actually stopped the walk (empty when found).
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.
structuralReasonCodesNo

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes far beyond the annotations (which only cover read-only/idempotent safety). It discloses that no signature is verified, that every link is marked PKI_REASON_SIGNATURE_NOT_CHECKED, that `valid` is therefore always false, that the verdict is `found` or PKI_REASON_NO_TRUST_ANCHOR, and that a negative result is never isError. These are the exact behavioral traps an agent would otherwise misread as failure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the purpose and the negative-result disclosure, then alternatives, then the token-frugal tip — an effective ordering. It is a single dense block with heavy parenthetical nesting, which costs some readability, but no sentence is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 12-parameter, deeply nested tool with an output schema, the description covers the one thing structure can't: that the verdict vocabulary here is `found`, not `valid`. Return shape is handled by the output schema and the description still calls out the summary-mode fields, so nothing an agent needs to invoke this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real nuance the schema does not: `purposes` only steer discovery while the leaf's EKU is judged by verify_certificate_chain, and `at` pinning buys cacheability. It also explains what verbosity:'summary' retains (found, structuralReasonCodes, pathLength).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource — RFC 5280 §6 path building from `leaf` through untrusted `candidates` to the caller's `trustAnchors` — and explicitly scopes out signature checking. It names the sibling tools (validate_certificate_path, verify_certificate_chain) that do the other half of the job, so an agent can separate this tool from its neighbors without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use ("Use it to learn which intermediates a chain needs") and names the two successor tools with the conditions that select them: validate_certificate_path for ordered-path signature checks, verify_certificate_chain for the full verdict with revocation. It also adds an operational guideline — pin `at` for a reproducible, cacheable result.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

check_certificate_nameCheck certificate name (RFC 6125 host / IP identity, or one name match)A
Read-onlyIdempotent

The RFC 6125 identity verdict, two forms. Certificate form: certificate + exactly one of host (DNS name as it appeared in the URL) / ip — is the certificate valid for that identity (subjectAltName dNSName / iPAddress; wildcards under allowWildcards, default true; allowCommonNameFallback, default false, reads a CN-only certificate's commonName and then needs chain — the issuing CAs, leaf's issuer first — so the CN is held to their dNSName name constraints as a SAN would be). Match form: presented (the certificate's dNSName, possibly a wildcard) + reference — one RFC 6125 §6.4 name comparison, no certificate. A negative verdict is valid:false + reasons[] (PKI_REASON_NAME_MISMATCH, …) in structuredContent — never isError. The name only: it is not trust (verify_certificate_chain with serverName is the full verdict), not validity, not revocation. Token-frugal: verbosity:'summary'.

NameRequiredDescriptionDefault
ipNoThe reference IPv4 or IPv6 address. Exclusive with host.
hostNoThe reference DNS name as it appeared in the URL (one trailing dot is ignored). Exclusive with ip.
chainNoThe issuing certificates, leaf's issuer first (every PEM block of every item is taken). Optional; read only by the commonName fallback, which holds a CN read as a host name to the dNSName name constraints of these CAs (without it the fallback refuses).
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
presentedNoMatch form: the certificate's dNSName, possibly a wildcard. With reference; exclusive with certificate / host / ip / chain.
referenceNoMatch form: the reference host name.
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
certificateNoThe certificate to judge (certificate form): exactly one of pem / derBase64 / path. With exactly one of host / ip. Exclusive with presented / reference.
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
allowWildcardsNoAccept a wildcard name (*.example.test) at all. Default true; false is right for an internal PKI that issues none.
allowCommonNameFallbackNoFall back to the subject commonName when the certificate carries no dNSName and no iPAddress (CA/Browser Forum forbids CN-only certificates since 2017; needs chain). Default false.

Output Schema

NameRequiredDescription
formNo
matchNoMatch form: whether presented matches reference.
validNoThe verdict (the match outcome in the match form). false is a negative verdict, never an error.
reasonsNoWhy the verdict is negative (empty when valid): the engine PKI_REASON_* codes, each with the standard it cites and the path of the offending element. A reason is returned, never thrown.
subjectNoCertificate form: RFC 4514 subject of the certificate.
presentedNoMatch form: the presented name.
referenceNoThe reference identity judged.
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
reasonCodesNo
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnly/idempotent annotations it discloses non-obvious behavior: a negative result is valid:false + reasons[] in structuredContent and never isError, defaults for allowWildcards and allowCommonNameFallback, and that the CN read is held to the issuing CAs' dNSName name constraints. It also flags verbosity:'summary' as the token-frugal path.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the two forms and the verdict semantics ahead of the caveats, and every clause carries information. The parenthetical chaining in the certificate-form sentence is heavy and would read better as separated sentences, but nothing is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return shape needn't be restated, yet the description still covers the negative-verdict contract and reasons codes. For a 14-parameter tool with nested inputs, the exclusivity rules, defaults, and prerequisite wiring are all present — nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds genuine meaning: `host` is 'as it appeared in the URL', `chain` is ordered leaf's issuer first and read only by the CN fallback, and the form-exclusivity rules (certificate with exactly one of host/ip, exclusive with presented/reference) are spelled out. Limits/strict/pemMode/encodingRules go unmentioned, which keeps it from a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource (RFC 6125 identity verdict over a certificate or a name pair) and enumerates its two disjoint forms with the exact parameters that select each. It distinguishes itself from verify_certificate_chain and from validity/revocation checking, so an agent can place it among the siblings without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit routing: certificate form vs match form, plus a clear 'not this tool' clause naming verify_certificate_chain with serverName as the full verdict. The CN fallback condition (needs `chain`, refuses without it) is stated as a prerequisite rather than left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

check_certificate_purposeCheck certificate purpose (extended key usage along the path)A
Read-onlyIdempotent

The RFC 5280 §4.2.1.12 purpose verdict: may certificate be used for purpose (serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, ocspSigning, any, or a dotted KeyPurposeId OID)? The path judged is [certificate, ...chain] (issuers, leaf's issuer first): under restrictIssuers (default true, what every Web PKI validator does) a CA's own extKeyUsage restricts what it issued; requireExplicitPurpose (default false) refuses a certificate with no extKeyUsage instead of reading its absence as unrestricted. A negative verdict is valid:false + reasons[] (PKI_REASON_PURPOSE_NOT_PERMITTED with the path[i] element) in structuredContent — never isError; an unknown purpose name is VALIDATION_ERROR. The purpose only: not trust, validity, names or revocation (verify_certificate_chain with purposes is the full verdict). Token-frugal: verbosity:'summary'.

NameRequiredDescriptionDefault
chainNoThe issuing certificates, leaf's issuer first (the path is [certificate, ...chain]) (every PEM block of every item is taken). Optional; their extKeyUsage restricts the leaf under restrictIssuers.
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
purposeYesThe purpose needed: serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, ocspSigning, any, or a dotted KeyPurposeId OID.
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
certificateYesThe end-entity certificate to judge: exactly one of pem / derBase64 / path.
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
restrictIssuersNoApply a CA's own extKeyUsage to what it issued (what every Web PKI validator does; no sentence of RFC 5280 requires it). Default true; false is the literal-RFC reading.
requireExplicitPurposeNoRequire the certificate to carry extKeyUsage and name the purpose explicitly (the stricter Web PKI reading); default false = no extKeyUsage means unrestricted (RFC 5280 §4.2.1.12).

Output Schema

NameRequiredDescription
validNoThe verdict. false is a negative verdict (reasons[]), never an error.
purposeNoThe purpose as given.
reasonsNoWhy the verdict is negative (empty when valid): the engine PKI_REASON_* codes, each with the standard it cites and the path of the offending element. A reason is returned, never thrown.
subjectNoRFC 4514 subject of the certificate.
pathLengthNo1 + the chain certificates (reasons name path[i]).
purposeOidNoThe KeyPurposeId OID judged.
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
purposeNameNoThe registry name of that OID.
reasonCodesNo
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover safety (readOnly, idempotent, non-destructive), and the description adds genuinely new behavior: a negative verdict arrives as valid:false plus reasons[] with PKI_REASON_PURPOSE_NOT_PERMITTED and the offending path[i], never as isError, while an unknown purpose name is a VALIDATION_ERROR. It also discloses the two policy flags' defaults and their real-world meaning. It does not discuss failure modes such as unparsable certificates or path-length violations, which keeps it just short of a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is a single dense paragraph, but front-loaded: the verdict question, the path shape, then the two policy flags, then the return/error contract, then the scope exclusion. The purpose enumeration is duplicated verbatim in the schema and the sentence about restrictIssuers partly restates its parameter description, so a small amount of text does not earn its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an 11-parameter tool with nested certificate/chain objects and an output schema, the description covers the path ordering ([certificate, ...chain], issuer-first), the input alternatives (pem/derBase64/path), the verdict contract, and the boundary against the broader verification tool. Since an output schema exists, it need not enumerate return fields, and it appropriately does not.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description earns above that by explaining the semantic consequence of `restrictIssuers` (a CA's extKeyUsage constrains what it issued, the Web PKI validator behavior, versus the literal-RFC reading) and `requireExplicitPurpose` (absence of extKeyUsage treated as unrestricted by default). It also enumerates the accepted purpose values and points at verbosity:'summary' for token frugality, though the 22 `limits` knobs and `fields` dot-paths are left entirely to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise verb+resource: the RFC 5280 §4.2.1.12 'purpose verdict' answering whether `certificate` may be used for `purpose`. It explicitly scopes itself against the sibling that gives the full verdict (`verify_certificate_chain with purposes`), so an agent can pick the right tool without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states both when to use it (extKeyUsage/purpose judgment along a path) and when not to: 'The purpose only: not trust, validity, names or revocation (verify_certificate_chain with purposes is the full verdict).' That is an explicit exclusion plus a named alternative, which is the top of the scale.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

check_crl_revocationCheck CRL revocation (one certificate against one CRL, optional delta)A
Read-onlyIdempotent

The RFC 5280 §6.3 revocation verdict of ONE certificate against ONE base crl you fetched (this server never fetches), evaluated at at: the list must be in scope for the certificate (issuer, issuing distribution point, reasons), fresh (nextUpdate, or staleTolerance ms past it), signature-verified, and the serial must not be listed (PKI_REASON_REVOKED with the reason otherwise). delta applies a delta CRL over the base (it must carry a deltaCRLIndicator — VALIDATION_ERROR otherwise — and name the base's number). issuer is the CRL issuer certificate: the base (and delta) signatures are verified against it and reported as signatureVerified / deltaSignatureVerified; without it the engine refuses the unverified list, so pass it for a real verdict. valid:false + reasons[] is a verdict, never isError. Pin at for a reproducible, cacheable verdict. SHA-1 signatures need allowSha1:true. Token-frugal: verbosity:'summary'. inspect_crl shows the list's facts; verify_certificate_chain with crls:[…] is the whole-chain form.

NameRequiredDescriptionDefault
atNoThe instant the verdict is evaluated at. An ISO 8601 date-time (UTC unless zoned; e.g. 2027-01-01T00:00:00Z), epoch milliseconds as digits, or 'now'. Omitted: PKINATIVE_MCP_AT when the operator pinned it, else the wall clock (a clock-dependent result is never cached).
crlYesThe base CRL (X509 CRL PEM or DER): exactly one of pem / derBase64 / path.
deltaNoA delta CRL applied over the base: exactly one of pem / derBase64 / path. Must carry a deltaCRLIndicator (VALIDATION_ERROR otherwise); its base CRL number is checked against the base.
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
issuerNoThe CRL issuer certificate: exactly one of pem / derBase64 / path. The base (and delta) signatures are verified against it and reported to the engine as signatureVerified. Omitted: the signatures are NOT verified and the engine does not accept the list as evidence (valid:false, PKI_REASON_REVOCATION_UNKNOWN) — the verdict is then only a dry run.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
allowSha1NoAccept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write.
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
certificateYesThe certificate to check: exactly one of pem / derBase64 / path.
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
staleToleranceNoMilliseconds past nextUpdate the evidence is still accepted (default: the engine policy, none).

Output Schema

NameRequiredDescription
validNotrue = the certificate is not revoked according to fresh, in-scope, signature-verified evidence. false is a negative verdict (reasons[]), never an error.
isDeltaNoWhether the BASE list itself carries a deltaCRLIndicator (false for a proper base CRL).
reasonsNoWhy the verdict is negative (empty when valid): the engine PKI_REASON_* codes, each with the standard it cites and the path of the offending element. A reason is returned, never thrown.
crlNumberNoThe base CRL number (decimal), null when absent.
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
evaluatedAtNoThe ISO instant used — re-pass it as `at` to reproduce the verdict.
reasonCodesNo
deltaCrlNumberNoThe delta CRL number (present only with `delta`).
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
evaluatedAtSourceNo
signatureVerifiedNoThe base CRL signature against `issuer`; null when no issuer was given (then the engine reports the list as unverified).
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.
deltaSignatureVerifiedNoThe delta CRL signature against `issuer` (present only with `delta`).

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Adds substantial behavior beyond the read-only/non-destructive annotations: scope and freshness checks, signature verification, delta CRL requirements, issuer behavior, valid:false semantics, SHA-1 gating, and cache reproducibility via at.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose and key constraints are front-loaded, and nearly every sentence carries operational value. It is dense and slightly overstuffed as a single paragraph, but appropriate for a 13-parameter cryptographic tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Complete for this tool's complexity: it explains inputs, verdict semantics, error-as-verdict behavior, alternatives, and reproducibility. With a rich schema and output schema present, nothing essential is missing from the description.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already explains each parameter. The description still adds meaningful cross-parameter semantics, especially why issuer is required for a real verdict and how delta must relate to the base CRL.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb, resource, scope, and standard: the RFC 5280 §6.3 revocation verdict of ONE certificate against ONE base CRL at a given time. It explicitly distinguishes itself from inspect_crl and the whole-chain verify_certificate_chain form.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Names the alternatives and when to use them: inspect_crl for list facts, verify_certificate_chain with crls for whole-chain verification. It also says the server never fetches CRLs and that issuer is required for a real verdict.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

check_ocsp_statusCheck OCSP status (one response for one certificate: CertID, responder, freshness, nonce)A
Read-onlyIdempotent

The RFC 6960 verdict of ONE OCSP response you fetched (this server never queries a responder) for ONE certificate: the CertID is recomputed from issuer (with the hash the response used, or hashAlgorithm) and must be answered; the signature is verified with responder, else the certificates embedded in the response, else the issuer itself; the signer must be authorised (RFC 6960 §4.2.2.2: the CA, a delegate the CA issued carrying id-kp-OCSPSigning, or responderTrusted:true for a trust you hold out of band); then the engine judges the status (good; PKI_REASON_REVOKED / unknown otherwise), freshness at at (thisUpdate / nextUpdate / producedAt, staleTolerance and futureTolerance ms), and the nonce (nonce must be echoed; requireNonce:true refuses a response without one). A non-successful response status is a negative verdict with status null. valid:false + reasons[] is a verdict, never isError. Pin at for a reproducible, cacheable verdict. Token-frugal: verbosity:'summary' (valid, reasonCodes, status, signatureVerified, responderAuthorized, responder). create_ocsp_request builds the request you POST; inspect_ocsp_response shows the facts without a verdict.

NameRequiredDescriptionDefault
atNoThe instant the verdict is evaluated at. An ISO 8601 date-time (UTC unless zoned; e.g. 2027-01-01T00:00:00Z), epoch milliseconds as digits, or 'now'. Omitted: PKINATIVE_MCP_AT when the operator pinned it, else the wall clock (a clock-dependent result is never cached).
nonceNoThe nonce you put in the request (1–32 bytes; create_ocsp_request summary.nonceHex): the response must echo it. Hexadecimal (an 0x prefix, colons and spaces are tolerated).
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
issuerYesThe CA certificate that issued it: exactly one of pem / derBase64 / path. Its name and key hashes form the CertID to match, and it is the authority a responder must be (or be delegated by).
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
responseYesThe OCSP response (DER, as the responder returned it): exactly one of pem / derBase64 / path.
allowSha1NoAccept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write.
responderNoThe responder certificate to verify the signature with: exactly one of pem / derBase64 / path. Omitted: the certificates embedded in the response are tried, then the issuer itself.
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
certificateYesThe certificate whose status is asked: exactly one of pem / derBase64 / path.
requireNonceNoRefuse a response that carries no nonce (RFC 8954).
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
hashAlgorithmNoThe CertID hash to recompute with. Default: the one the response used for this serial, else SHA-1.
staleToleranceNoMilliseconds past nextUpdate the evidence is still accepted (default: the engine policy, none).
futureToleranceNoMilliseconds a thisUpdate / producedAt may lie after `at` (clock skew) and still be accepted (default: the engine policy).
responderTrustedNoState an out-of-band trust in the responder (RFC 6960 §4.2.2.2 third case): responderAuthorized is then true whatever its certificate says.

Output Schema

NameRequiredDescription
validNotrue = a fresh, correctly signed, authorised answer says good. false is a negative verdict (reasons[]: revoked, unknown, stale, unauthorised, unverified, nonce …), never an error.
statusNoThe status the response gives for this serial; null when the response holds no answer for it.
reasonsNoWhy the verdict is negative (empty when valid): the engine PKI_REASON_* codes, each with the standard it cites and the path of the offending element. A reason is returned, never thrown.
certIdHexNoThe recomputed DER CertID (hex).
responderNoThe subject of the certificate that verified the signature.
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
evaluatedAtNoThe ISO instant used — re-pass it as `at` to reproduce the verdict.
reasonCodesNo
hashAlgorithmNoThe hash the CertID was recomputed with.
diagnosticCountNo
responderSourceNoWhere the signer came from: the `responder` input, the certificates embedded in the response, or the issuer itself.
diagnosticCountsNoOccurrences per diagnostic code.
evaluatedAtSourceNo
signatureVerifiedNoA responder candidate verified the response signature.
responderAuthorizedNoThe signer is the CA itself, a delegate the CA issued with id-kp-OCSPSigning, or responderTrusted was set.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Discloses behavior well beyond the annotations: the CertID recomputation and required match, the signature-verification fallback chain (responder, then embedded certs, then issuer), the RFC 6960 §4.2.2.2 authorization rule including responderTrusted, freshness windows with staleTolerance/futureTolerance, and nonce echo/requireNonce. It also states a crucial agent-facing contract: valid:false + reasons[] is a verdict, never isError, and offers verbosity:'summary' as a token-frugal mode.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the verdict statement and scoping caveat, then ordered by verification pipeline, with sibling routing and the token-frugal hint appended. The middle is a single semicolon-chained paragraph with deeply nested parentheticals that is dense to parse, but nearly every clause carries non-redundant information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex 18-parameter tool with nested inputs, the description covers the decision logic an agent cannot infer: verification order, trust fallbacks, freshness policy, nonce policy, negative-status handling, and the error-vs-verdict distinction. Return shapes are handled by the output schema and safety profile by annotations, so nothing essential is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3; the description goes beyond it by explaining cross-parameter semantics — issuer derives the CertID with either the response's hash or hashAlgorithm, nonce must be echoed by the response, and responder is only needed when the embedded chain is insufficient. It adds real interpretive value rather than restating field docs, though some 18-parameter surface (limits, encodingRules, pemMode) is left entirely to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: it delivers the RFC 6960 verdict for one OCSP response against one certificate. It also scopes the tool sharply ('this server never queries a responder') and names the two sibling tools it is not (create_ocsp_request, inspect_ocsp_response), so an agent can route correctly without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly positions the tool in a workflow: create_ocsp_request builds the request, inspect_ocsp_response shows facts without a verdict, and this tool judges. It also advises pinning `at` for reproducible, cacheable verdicts. It does not spell out when a caller should skip OCSP checking entirely (e.g. preferring CRL via check_crl_revocation), so it falls short of full when/when-not coverage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

compute_fingerprintCompute fingerprint (DER digest, key identifier, SHAKE256)A
Read-onlyIdempotent

Read-only digest of ONE object. mode:'fingerprint' (default) hashes the exact DER of the input (SHA-256 by default; SHA-1 / SHA-384 / SHA-512; webcrypto:true uses Web Crypto, same result) — what openssl x509 -fingerprint prints; mode:'key-id' computes the RFC 5280 §4.2.1.2 key identifier (SHA-1 by default, or SHA-256) of the public key found in a certificate, a CSR or a PUBLIC KEY, reporting where it came from; mode:'shake256' squeezes outputLength bytes (default 32, ≤ 1048576) of SHAKE256 — the digest of an Ed448 CMS signer. hex is lowercase without separators, formatted follows separator (':' default) and letterCase (upper default). Any PEM label or DER is accepted (a private key may be digested: the digest reveals nothing and no input byte is returned); a bundle of several blocks is refused (extract one with convert_pem). Refuses: an algorithm outside its mode, SHA-384/512 for a key identifier, outputLength or webcrypto outside their mode (VALIDATION_ERROR); key-id on an object that holds no public key (PKI_X509_* / PKI_ASN1_*). Token-frugal: verbosity:'summary', fields:[…].

NameRequiredDescriptionDefault
modeNo'fingerprint' (default): the digest of the exact DER. 'key-id': the RFC 5280 §4.2.1.2 identifier of the public key of a certificate, a CSR or a PUBLIC KEY. 'shake256': SHAKE256 output of `outputLength` bytes.fingerprint
inputYesThe object to digest (any PEM label, or DER): exactly one of pem / derBase64 / path. A bundle of several blocks is refused: extract one with convert_pem.
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
algorithmNofingerprint: SHA-1 | SHA-256 (default) | SHA-384 | SHA-512. key-id: SHA-1 (default, RFC 5280 method 1) | SHA-256. Not for shake256.
separatorNoPlaced between octets of `formatted` (default ':'; '' for none).:
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
webcryptoNofingerprint only: digest through Web Crypto instead of the pure-TypeScript hashes (identical result).
letterCaseNoHex case of `formatted` (`hex` is always lowercase).upper
outputLengthNoshake256 only: output bytes (default 32, ≤ 1048576; 64 for an Ed448 CMS signer).
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der

Output Schema

NameRequiredDescription
hexNoThe digest as lowercase hex, no separator.
fromNokey-id: where the public key came from.
modeNo
labelNoThe PEM label of the input, null for DER.
algorithmNoSHA-1 | SHA-256 | SHA-384 | SHA-512, or 'SHAKE256'.
formattedNoThe digest with the separator and case asked for (AB:CD:… by default).
sizeBytesNoBytes digested: the DER of the input (fingerprint, shake256) or the public key bits (key-id).
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
outputLengthNoshake256: the output length in bytes.
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover safety (readOnly, idempotent, non-destructive), yet the description still adds real context: which algorithm×mode combinations produce VALIDATION_ERROR, which inputs yield PKI_X509_*/PKI_ASN1_* errors, that a private key may be digested without returning input bytes, and the hex/formatted formatting contract. It stops short of describing the response fields (which the output schema covers), so a 4 rather than 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core statement, then mode-by-mode detail, then output format, then refusals — a sensible order. It is a dense single block with em-dash clauses that reward careful reading, but no sentence is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 13-parameter tool with an output schema and full annotation coverage, the description supplies everything an agent needs to pick a mode, avoid validation errors, and interpret the two hex outputs. Nothing material is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3; the description nonetheless adds cross-cutting meaning — algorithm defaults differ per mode, `hex` is always lowercase while `formatted` obeys separator and letterCase (upper default), outputLength default 32 with the 1048576 cap, and webcrypto is fingerprint-only with identical results. That is genuine semantic value, though `limits`, `strict`, `pemMode`, and `encodingRules` are left entirely to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a precise verb+resource+scope ('Read-only digest of ONE object') and then defines each of the three modes concretely: DER fingerprint, RFC 5280 key identifier, and SHAKE256 squeeze. The reference to what `openssl x509 -fingerprint` prints anchors it against a behavior an agent already knows.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Mode selection is spelled out with defaults and per-mode constraints (key-id over cert/CSR/PUBLIC KEY; shake256 for an Ed448 CMS signer output). It also names the alternative for a common failure case — a multi-block bundle is refused, 'extract one with convert_pem' — which is explicit routing to a sibling.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

convert_pemConvert PEM (list and extract blocks, or wrap DER)A
Idempotent

The RFC 7468 layer. mode:'decode' lists every block of a PEM text (index, label, DER size, offset, RFC 1421 headers under pemMode:'lax', SHA-256 of the DER) — label keeps one kind, extractIndex also delivers that block as an artefact (encoding 'der' by default, 'pem' or 'hex'; inline as an embedded resource, or as a sandbox file with outputMode:'file' + outputPath, never overwritten). mode:'encode' wraps DER bytes (derBase64 or a DER file path) into one strict PEM block under label (required) and delivers it as the artefact (64-character lines, LF). A private-key block (PRIVATE KEY, ENCRYPTED PRIVATE KEY, RSA/EC/DSA PRIVATE KEY) is listed by label and size but never extracted, and never produced: SECURITY_VIOLATION (use inspect_private_key) — DER that parses as a PKCS#8 key or a PKCS#12 file is refused under ANY label. Refuses: derBase64 in decode, pem in encode, an encoding other than 'pem' in encode, a file output without extractIndex (VALIDATION_ERROR); an index past the last block (NOT_FOUND); a label absent from the text (PKI_PEM_UNEXPECTED_LABEL); a malformed text (PKI_PEM_* — pemMode:'lax' tolerates whitespace, line length and headers; a missing base64 padding is refused in both modes). Token-frugal: verbosity:'summary' (count + labels), fields:[…].

NameRequiredDescriptionDefault
modeYes'decode' lists the blocks of a PEM text (and extracts one with extractIndex); 'encode' wraps DER bytes into one PEM block.
inputYesThe PEM text (decode: pem or a path to a PEM file) or the DER bytes (encode: derBase64 or a path to a DER file): exactly one of pem / derBase64 / path.
labelNodecode: list only the blocks with this label. encode: the label of the block (required; e.g. 'CERTIFICATE'). Upper-case words and digits separated by single spaces; a private-key label is refused (SECURITY_VIOLATION).
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
encodingNoArtefact encoding. decode + extractIndex: 'der' (default, raw bytes), 'pem' (the block re-emitted strictly) or 'hex' (inline only, ≤ 64 KiB). encode: always 'pem' (anything else is VALIDATION_ERROR).
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
outputModeNo'base64' (default) returns the artefact inline as an embedded resource (≤ 50 MiB). 'file' writes it inside PKINATIVE_MCP_OUTPUT_DIR (never overwrites; ≤ 1 GiB) and returns a resource_link.base64
outputPathNoRelative path inside the sandbox (only when outputMode='file'); the extension must match the encoding (.der .crt .cer .csr .crl .p7s .bin … for der; .pem for pem). Key-bearing extensions (.key .p8 .p12 .pfx) are refused: nothing here writes a private key.
extractIndexNodecode: also deliver the block with this index (its position in the text, as listed) as an artefact in `encoding`. A private-key block cannot be extracted (SECURITY_VIOLATION).
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der

Output Schema

NameRequiredDescription
modeNo
countNodecode: number of blocks listed.
labelNoencode: the label written.
blocksNodecode: every block of the text (or those with `label`), in order. A private-key block is listed by label and size only.
labelsNodecode: the label of every block listed.
sha256Noencode: SHA-256 of the DER wrapped (lowercase hex).
artefactNoThe artefact delivered alongside the report: its facts here, its bytes once as the embedded resource (or the resource_link in file mode).
extractedNodecode: the block delivered as the artefact.
sizeBytesNoencode: the DER size wrapped.
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Far beyond the annotations: it discloses the SECURITY_VIOLATION refusal for any private-key block (listed but never extracted or produced), the PKCS#8/PKCS#12 rejection under any label, the never-overwrite file behaviour, and the exact error codes for each invalid combination. Annotations only say it's non-readOnly, idempotent and non-destructive, so this description carries real behavioral weight.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is front-loaded with the two modes and scoping, and every clause carries information (no filler). The long em-dash chains make it dense to parse, which costs it a point, but nothing is redundant.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With a full output schema, rich annotations, and 100% schema coverage, the description still supplies everything an agent needs: mode semantics, artefact/encoding behaviour, security refusals, error taxonomy, and token-frugal options. Nothing required to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds cross-parameter rules the schema does not state: derBase64 is refused in decode, pem is refused in encode, a file output without extractIndex is a VALIDATION_ERROR, and encoding must be 'pem' in encode. These conditional interactions materially improve parameter understanding.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

It names the exact standard layer ('RFC 7468') and states both verbs precisely: decode lists/ extracts every PEM block, encode wraps DER into one strict PEM block. The distinction between the two modes is unmistakable and it explicitly routes private-key handling to the inspect_private_key sibling.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives clear conditions for each mode and names one alternative ('use inspect_private_key') for the private-key case, plus a detailed refusal list that defines misuse. It doesn't differentiate itself from other DER/PEM-adjacent siblings like decode_asn1 or encode_asn1, so it stops short of full when/when-not coverage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_certificateCreate certificate (CA or leaf, from a JSON spec, Web Crypto signed)A

Issue an X.509 v3 certificate from spec (subject, validity, extensions — basicConstraints, keyUsage, extendedKeyUsage, subjectAltName, issuerAltName, SKI / AKI, raw), signed through signer (a PKCS#8 key or a PKCS#12 file, imported as a NON-EXTRACTABLE Web Crypto key — nothing generates or exports a key here). Self-signed when no issuerCertificate is given (the subject key is derived from a plain signer.key, or taken from subjectPublicKey); issued when issuerCertificate is given (subjectPublicKey = a certificate, a CSR or a PUBLIC KEY is then required; the AKI is computed from the issuer). Serial 'random' by default (node:crypto, reported as summary.randomSerial), notBefore defaults to PKINATIVE_MCP_AT or now (whole seconds) — pin both for reproducible bytes. The new certificate is verified against the issuer (or itself) BEFORE it is returned (KEY_MISMATCH otherwise). RSA keys need signer.rsaScheme; SHA-1 needs allowSha1:true. Output PEM (default), DER or hex, inline or as a sandbox file (never overwritten).

NameRequiredDescriptionDefault
specYesWhat to write into the certificate. Unknown members are refused.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
signerYesThe signing key: exactly one of `key` (a PKCS#8 private key, plain or PBES2-encrypted, PEM or DER) or `p12` (a PKCS#12 file, PBES2 + PBMAC1 only). The key is imported into a NON-EXTRACTABLE Web Crypto key and never leaves the engine; no result ever carries a key byte. The algorithm follows the key: ECDSA on the key's curve, Ed25519/Ed448 (no hash), RSA REQUIRES rsaScheme ('pkcs1' | 'pss' — pkinative has no default). SHA-1 only with the tool's allowSha1:true.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
encodingNoArtefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'pem'.pem
allowSha1NoAccept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write.
outputModeNo'base64' (default) returns the artefact inline as an embedded resource (≤ 50 MiB). 'file' writes it inside PKINATIVE_MCP_OUTPUT_DIR (never overwrites; ≤ 1 GiB) and returns a resource_link.base64
outputPathNoRelative path inside the sandbox (only when outputMode='file'); the extension must match the encoding (.pem for pem; .crt .cer .der for der). Key-bearing extensions (.key .p8 .p12 .pfx) are refused: nothing here writes a private key.
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
subjectPublicKeyNoThe subject public key: a certificate, a CSR (CERTIFICATE REQUEST) or a PUBLIC KEY (SPKI): exactly one of pem / derBase64 / path. Omitted: derived from a PLAIN signer.key (self-signed only).
issuerCertificateNoThe issuing CA certificate: exactly one of pem / derBase64 / path. Omitted: the certificate is self-signed (issuer = subject, the signing key must be the subject's).

Output Schema

NameRequiredDescription
hexNoThe artefact as lowercase hex (when encoding='hex').
modeYes
sha256YesSHA-256 of the DER bytes (lowercase hex) — the reproducibility proof.
summaryYes
encodingYes
filePathNoSandboxed absolute path (when outputMode='file').
sizeBytesYesSize of the DER artefact.
diagnosticsYesNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
diagnosticCountsYesOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations: the key is imported as a NON-EXTRACTABLE Web Crypto key, nothing generates or exports key material, the certificate is verified against the issuer BEFORE returning (KEY_MISMATCH otherwise), serial randomness comes from node:crypto and is reported as summary.randomSerial, and sandbox files are never overwritten. That is exactly the write/verify/irreversibility context annotations cannot express.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and scoped tightly to what matters, with no filler sentences. It is a single dense paragraph with nested parentheticals and semicolon-chained clauses, which makes it harder to scan than a short labelled list would be.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Complete for a 12-parameter, deeply nested, mutation tool: mode selection, key handling, verification, encoding, output destination and limit-ceiling behavior are all covered, and with an output schema present it does not waste space describing return values beyond the one field an agent must know (summary.randomSerial).

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, yet the description adds cross-field meaning the schema states only piecemeal: which inputs are mutually exclusive, that the subject key is derived from a plain signer.key or from subjectPublicKey depending on mode, and that AKI is computed from the issuer. Pinning notBefore/notAfter for reproducible bytes is a genuinely non-obvious usage note.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb and resource: 'Issue an X.509 v3 certificate from `spec` ... signed through `signer`', and immediately distinguishes the two modes (self-signed vs issued) and the format set. An agent can tell this apart from create_csr, sign_cms and verify_certificate_chain without inspecting any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

States the conditions that switch modes ('Self-signed when no issuerCertificate is given ... issued when issuerCertificate is given'), the prerequisite for RSA ('RSA keys need signer.rsaScheme') and the SHA-1 gate. It never explicitly names a sibling alternative ('use create_csr instead when ...'), so routing guidance is implied through conditions rather than stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_csrCreate CSR (PKCS#10 request from a JSON spec, Web Crypto signed)A

Build a PKCS#10 certification request (RFC 2986) from spec (subject; the extensions to request — subjectAltName, keyUsage, extendedKeyUsage, basicConstraints, subjectKeyIdentifier, raw — carried in extensionRequest; issuerAltName / authorityKeyIdentifier are refused as the issuer's), signed through signer (a PKCS#8 key or a PKCS#12 file imported as a NON-EXTRACTABLE Web Crypto key; nothing generates or exports a key here). The public key certified is the signing key's own: derived from a plain signer.key, taken from the PKCS#12 certificate, or given as subjectPublicKey (a certificate, a CSR or a PUBLIC KEY — required for an encrypted PKCS#8 key, whose type then comes from it). The proof of possession is verified BEFORE the request is returned: KEY_MISMATCH when the key and the public key differ. RSA keys need signer.rsaScheme; an encrypted key needs signer.password (or PKINATIVE_MCP_PASSWORD); SHA-1 needs allowSha1:true. ECDSA / RSA-PSS signatures are randomised (PRODUCES). Output PEM (default), DER or hex, inline or as a sandbox file (never overwritten). inspect_csr reads it back; create_certificate issues from it (subjectPublicKey = the request).

NameRequiredDescriptionDefault
specYesWhat to write into the request: the subject and the extensions to ask for. Unknown members are refused.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
signerYesThe signing key: exactly one of `key` (a PKCS#8 private key, plain or PBES2-encrypted, PEM or DER) or `p12` (a PKCS#12 file, PBES2 + PBMAC1 only). The key is imported into a NON-EXTRACTABLE Web Crypto key and never leaves the engine; no result ever carries a key byte. The algorithm follows the key: ECDSA on the key's curve, Ed25519/Ed448 (no hash), RSA REQUIRES rsaScheme ('pkcs1' | 'pss' — pkinative has no default). SHA-1 only with the tool's allowSha1:true.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
encodingNoArtefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'pem'.pem
allowSha1NoAccept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write.
outputModeNo'base64' (default) returns the artefact inline as an embedded resource (≤ 50 MiB). 'file' writes it inside PKINATIVE_MCP_OUTPUT_DIR (never overwrites; ≤ 1 GiB) and returns a resource_link.base64
outputPathNoRelative path inside the sandbox (only when outputMode='file'); the extension must match the encoding (.pem for pem; .csr .p10 .der for der). Key-bearing extensions (.key .p8 .p12 .pfx) are refused: nothing here writes a private key.
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
subjectPublicKeyNoThe public key to certify: a certificate, a CSR (CERTIFICATE REQUEST) or a PUBLIC KEY (SPKI): exactly one of pem / derBase64 / path. Omitted: derived from a PLAIN signer.key or taken from the PKCS#12 certificate; required for an encrypted PKCS#8 key. It must be the signing key's public half (a CSR is a proof of possession).

Output Schema

NameRequiredDescription
hexNoThe artefact as lowercase hex (when encoding='hex').
modeYes
sha256YesSHA-256 of the DER bytes (lowercase hex) — the reproducibility proof.
summaryYes
encodingYes
filePathNoSandboxed absolute path (when outputMode='file').
sizeBytesYesSize of the DER artefact.
diagnosticsYesNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
diagnosticCountsYesOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Adds substantial behavior beyond the annotations: the signing key is imported as a non-extractable Web Crypto key, proof of possession is verified before return, KEY_MISMATCH occurs on key/public-key mismatch, RSA requires `rsaScheme`, encrypted keys require a password or environment variable, SHA-1 requires `allowSha1`, and ECDSA/RSA-PSS signatures are randomised. The randomisation note is consistent with `idempotentHint=false`, and the write-like behavior is consistent with `readOnlyHint=false` and `destructiveHint=false` because output files are never overwritten.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose is front-loaded, and the description remains dense with no obvious filler. The semicolon-heavy single paragraph is lengthy, but most of the detail is justified by the tool’s complexity and the number of cryptographic constraints.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Because an output schema exists, the description need not document return values, and it still adds output-encoding, inline/file, and non-overwrite behavior. With comprehensive annotations and 100% schema coverage, there is no major gap for an agent to call the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents all 11 parameters, including the full `limits` object. The description reinforces useful cross-parameter constraints — public-key derivation from `signer.key` / PKCS#12 / `subjectPublicKey`, encrypted-key requirements, RSA `rsaScheme`, and output modes — though much of this is already present in the schema, so it adds only moderate value.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Starts with a specific verb and resource — “Build a PKCS#10 certification request (RFC 2986)” — and identifies the two required inputs (`spec`, `signer`). It also distinguishes the tool from related lifecycle steps by noting that `inspect_csr` reads the result back and `create_certificate` issues from it.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names downstream alternatives (`inspect_csr`, `create_certificate`) and states important boundaries: nothing generates or exports a key here, and issuerAltName / authorityKeyIdentifier are refused because they belong to the issuer. This gives an agent clear context for when this construction step is appropriate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_ocsp_requestCreate OCSP request (RFC 6960 DER, SHA-1 or SHA-256 CertID, nonce)A

Build the DER OCSPRequest (RFC 6960 §4.1) asking a responder for the status of certificate, identified by the CertID computed from issuer (the CA that issued it — its name and key hashes) and the serial. hashAlgorithm 'SHA-1' (default, the engine default and what every responder answers) or 'SHA-256' (recommended where the responder supports it). nonce adds the RFC 8954 nonce extension: 'random' (16 bytes from node:crypto, summary.randomNonce:true) or your own hex (1–32 bytes); keep summary.nonceHex to demand it back with check_ocsp_status. certIdOnly:true returns the bare CertID (der / hex). This server never sends anything: POST the bytes yourself (Content-Type application/ocsp-request) to the URL in the certificate's authorityInfoAccess (inspect_certificate), then judge the answer with check_ocsp_status { response, certificate, issuer, nonce }. summary.certIdHex is the CertID to match in the response. Output DER (default, .orq), PEM or hex, inline or as a sandbox file (never overwritten).

NameRequiredDescriptionDefault
labelNoThe PEM label for encoding:'pem' (default OCSP REQUEST; upper-case words and digits). A private-key label is refused.
nonceNoThe RFC 8954 nonce extension: 'random', or hex of 1–32 bytes. Omitted: no nonce. Keep summary.nonceHex to match the response (check_ocsp_status nonce).
issuerYesThe CA certificate that issued it (its name and key are hashed into the CertID): exactly one of pem / derBase64 / path.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
encodingNoArtefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'der'.der
certIdOnlyNoReturn the bare DER CertID (RFC 6960 §4.1.1) instead of the request (der or hex only — a CertID has no PEM label).
outputModeNo'base64' (default) returns the artefact inline as an embedded resource (≤ 50 MiB). 'file' writes it inside PKINATIVE_MCP_OUTPUT_DIR (never overwrites; ≤ 1 GiB) and returns a resource_link.base64
outputPathNoRelative path inside the sandbox (only when outputMode='file'); the extension must match the encoding (.orq .der .bin for der). Key-bearing extensions (.key .p8 .p12 .pfx) are refused: nothing here writes a private key.
certificateYesThe certificate whose status to ask for: exactly one of pem / derBase64 / path.
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
hashAlgorithmNoThe CertID hash. 'SHA-1' (default — the engine default and what every responder answers; RFC 6960 §4.3) or 'SHA-256' (recommended, RFC 6960 §4.3 / RFC 5019 note: some responders still answer SHA-1 only).SHA-1

Output Schema

NameRequiredDescription
hexNoThe artefact as lowercase hex (when encoding='hex').
modeYes
sha256YesSHA-256 of the DER bytes (lowercase hex) — the reproducibility proof.
summaryYes
encodingYes
filePathNoSandboxed absolute path (when outputMode='file').
sizeBytesYesSize of the DER artefact.
diagnosticsYesNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
diagnosticCountsYesOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare readOnlyHint=false with destructiveHint=false, so the safety profile is already covered. The description adds real context beyond that: no network I/O, output files are never overwritten, nonce is 16 bytes from node:crypto, and sandbox extension restrictions. However it does not discuss error conditions (e.g. LIMIT_CEILING_EXCEEDED or strict-mode failures) that the 22 limit parameters imply. Solid additional context, but not exhaustive for the mutation surface.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded and there is no filler, but the single dense paragraph crams encoding, output mode, nonce, certIdOnly and the POST workflow into one block. Slight over-packing of backticked identifiers reduces scanability. No wasted sentences.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 13-parameter tool with nested objects, an output schema and 100% schema coverage, the description supplies the missing pieces an agent needs: the offline nature of the server, the external POST step, and the nonce/CertID matching protocol. Nothing essential to correct invocation is absent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description nonetheless adds value by explaining why SHA-1 is the default versus SHA-256, the nonce 1–32 byte constraint and its round-trip role via summary.nonceHex, and certIdOnly's bare-CertID output (der/hex only). It adds rationale and cross-tool linkage the schema cannot express. Small deduction for restating several schema-documented defaults verbatim.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

First sentence names a specific verb (build) and artefact (DER OCSPRequest, RFC 6960 §4.1) plus the two inputs that define the CertID, which is unambiguous and distinguishable from check_ocsp_status and inspect_ocsp_response. An agent can identify the tool without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states this server never sends anything, tells the agent to POST the bytes to the URL from authorityInfoAccess (obtained via inspect_certificate), then judge the answer with check_ocsp_status. It routes to both prerequisite and successor siblings with the exact parameters to carry forward.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_timestamp_requestCreate time-stamp request (RFC 3161 TimeStampReq over data or a digest)A

Build the RFC 3161 TimeStampReq you POST to a TSA yourself (Content-Type application/timestamp-query; this server never talks to a network): the message imprint is hashAlgorithm (SHA-256 default, SHA-384, SHA-512) over data, or digestHex already computed with it (wrong length → VALIDATION_ERROR). nonce:'random' draws 63 bits from node:crypto (summary.randomNonce:true; never cached) — without a nonce nothing ties the response to THIS request; a decimal or 0x-hex value pins it. policy names the TSA policy, certReq (default true) asks for the TSA certificate in the token. For a counter time-stamp over a CMS signature, the data is the signer's SIGNATURE VALUE (inspect_cms fields:['signerInfos.signature']), never the document. Keep the request bytes (or summary.imprintHex and nonce): verify_timestamp checks the response against them. Output DER (default; .tsq), PEM or hex, inline or as a sandbox file (never overwritten).

NameRequiredDescriptionDefault
dataNoThe data to time-stamp (hashed here with hashAlgorithm; for a counter time-stamp, the signer's signature value from inspect_cms): exactly one of text / base64 / path.
labelNoThe PEM label for encoding:'pem' (default TIMESTAMP REQUEST; upper-case words and digits). A private-key label is refused.
nonceNoA nonce the TSA must echo (RFC 3161 §2.4.1): 'random' (63 bits from node:crypto — summary.randomNonce reports it; such a call is never cached), a decimal or an 0x-hex integer. Without one, nothing ties a response to THIS request.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
policyNoThe TSA policy to issue under (dotted OID); the TSA's default when absent.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
certReqNoAsk the TSA to include its certificate in the token (what verify_timestamp needs unless you pass candidates).
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
encodingNoArtefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'der'.der
digestHexNoThe digest to stamp, already computed with hashAlgorithm (its length must match). Exclusive with data. Hexadecimal (an 0x prefix, colons and spaces are tolerated).
outputModeNo'base64' (default) returns the artefact inline as an embedded resource (≤ 50 MiB). 'file' writes it inside PKINATIVE_MCP_OUTPUT_DIR (never overwrites; ≤ 1 GiB) and returns a resource_link.base64
outputPathNoRelative path inside the sandbox (only when outputMode='file'); the extension must match the encoding (.tsq .der .bin for der; .pem for pem). Key-bearing extensions (.key .p8 .p12 .pfx) are refused: nothing here writes a private key.
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
hashAlgorithmNoThe message imprint algorithm.SHA-256

Output Schema

NameRequiredDescription
hexNoThe artefact as lowercase hex (when encoding='hex').
modeYes
sha256YesSHA-256 of the DER bytes (lowercase hex) — the reproducibility proof.
summaryYes
encodingYes
filePathNoSandboxed absolute path (when outputMode='file').
sizeBytesYesSize of the DER artefact.
diagnosticsYesNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
diagnosticCountsYesOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations: states an error contract (wrong digest length → VALIDATION_ERROR), a caching guarantee ('nonce never cached', consistent with idempotentHint=false), a no-overwrite guarantee on sandbox files, refusal of key-bearing extensions, and a size ceiling on hex output. It also reinforces openWorldHint=false with the explicit 'never talks to a network' statement rather than contradicting it.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded in the first clause and every sentence carries actionable content. It is, however, a single dense paragraph with deeply nested parentheticals and backticked fragments, which makes it harder to scan than a lightly segmented version.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 14-parameter, zero-required builder with a full output schema, the description covers the entire workflow end to end: input mode selection, nonce/policy/certReq choices, encoding and output destination, the error case, and the handoff to verify_timestamp. Remaining knobs (limits, strict, pemMode, encodingRules) are fully documented in the schema and need no repetition here.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description earns above that by explaining the mutual exclusivity semantics (data vs digestHex), the default SHA-256 imprint over data, and nonce behavior ('random' = 63 bits from node:crypto surfaced in summary.randomNonce, vs a pinned decimal/0x value). It adds the 'why' for nonce and certReq that the schema alone does not convey.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and artifact: 'Build the RFC 3161 TimeStampReq you POST to a TSA yourself', and immediately clarifies scope ('this server never talks to a network'). This cleanly separates it from inspect_timestamp (which reads a response) and verify_timestamp (which validates one), so an agent can route correctly without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly gives the when: you must POST the query to a TSA yourself. It also names alternatives and the downstream handoff ('verify_timestamp checks the response against them'), plus a concrete conditional branch — for counter time-stamps feed the SIGNATURE VALUE from inspect_cms, 'never the document'.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

decode_asn1Decode ASN.1 (node tree, typed read, re-encode)A
Read-onlyIdempotent

Read-only X.690 view of any DER object (BER with encodingRules:'ber'): the node tree — offset, header and content lengths, tag class / number / name, constructed flag, child count, a hex preview of each primitive (maxPreviewBytes, default 32) and the dotted value + registered name of every OBJECT IDENTIFIER — depth-first, capped at 20000 nodes (truncated:true). nodePath ('0.2.1' = child indices from the root; each node's path is what to pass) narrows to a subtree; read applies a typed reader to the selected node (boolean, integer, small-integer, enumerated, null, bit-string, octet-string, oid, relative-oid, string with stringType for an IMPLICIT tag, time with timeType) and returns value in wire form (bigint as decimal string, bytes as hex, time as epoch ms + ISO); reencode:true re-encodes the selected node through the engine and delivers it as an artefact (der / hex / pem for a labelled whole input; inline or a sandbox file) with reencode.identical; sequence:true decodes several top-level values back to back; allowTrailingData:true tolerates bytes after a single value. Refuses a private key or a PKCS#12 file — by PEM label, or when the DER parses as PKCS#8 / PKCS#12 — with SECURITY_VIOLATION (inspect_private_key / inspect_pkcs12 report them safely); a missing nodePath step is NOT_FOUND naming the range; a wrong reader tag is PKI_ASN1_UNEXPECTED_TAG; an IMPLICIT string / time without stringType / timeType is PKI_API_MISUSE; X.690 violations are PKI_ASN1_* (BER forms: encodingRules:'ber'). Token-frugal: verbosity:'summary' (node count, root tag, value), fields:['nodes.path','nodes.tagName'].

NameRequiredDescriptionDefault
readNoApply this typed reader to the selected node (the root without nodePath) and return `value`; a wrong tag is PKI_ASN1_UNEXPECTED_TAG.
inputYesThe DER / BER object (any PEM label except a private key, or DER): exactly one of pem / derBase64 / path. A private key or a PKCS#12 file is refused (SECURITY_VIOLATION).
labelNoreencode + encoding:'pem': the PEM label of the artefact (required when the input had no label or nodePath selects a sub-node; upper-case words and digits). A private-key label is refused.
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
encodingNoArtefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'der'.der
nodePathNoChild indices from the root, dot-separated ('0.2.1' = children[0].children[2].children[1]): the subtree listed, read or re-encoded. Omitted: the root. NOT_FOUND names the available range.
reencodeNoRe-encode the selected node through the engine encoder and deliver it as an artefact (`encoding` der by default, hex, or pem under the input label or `label`); `reencode.identical` says whether the bytes are the input bytes.
sequenceNoThe input is several top-level values back to back: decode each (exclusive with nodePath / read / reencode).
timeTypeNoread:'time' on an IMPLICIT-tagged node: the time type (required then).
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
outputModeNo'base64' (default) returns the artefact inline as an embedded resource (≤ 50 MiB). 'file' writes it inside PKINATIVE_MCP_OUTPUT_DIR (never overwrites; ≤ 1 GiB) and returns a resource_link.base64
outputPathNoRelative path inside the sandbox (only when outputMode='file'); the extension must match the encoding (.der .bin .asn1 for der; .pem for pem). Key-bearing extensions (.key .p8 .p12 .pfx) are refused: nothing here writes a private key.
stringTypeNoread:'string' on an IMPLICIT-tagged node: the charset to decode with (required then — PKI_API_MISUSE otherwise).
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
maxPreviewBytesNoContent bytes of a primitive shown in `preview` (hex; 0 hides previews; default 32).
allowTrailingDataNoAccept bytes after the outermost value instead of PKI_ASN1_TRAILING_DATA (single value only; sequence:true decodes them instead).

Output Schema

NameRequiredDescription
labelNoThe PEM label of the input, null for DER.
nodesNoThe selected subtree (or every top-level tree with sequence:true), depth-first in input order.
valueNoread: the typed value. boolean → boolean; integer / enumerated → decimal string; small-integer → number; null → null; bit-string → { hex, unusedBits }; octet-string → hex; oid / relative-oid → dotted string (name for a registered OID); string → { stringType, value }; time → { type, epochMilliseconds, iso, text }.
rootTagNotagName of the first node listed (null when nothing was listed).
artefactNoThe artefact delivered alongside the report: its facts here, its bytes once as the embedded resource (or the resource_link in file mode).
reencodeNo
identicalNoreencode: the bytes are identical.
nodeCountNo
sizeBytesNoBytes of the input object.
truncatedNotrue when the listing stopped at 20000 nodes (use nodePath to walk the rest).
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, yet the description still carries real weight: refusal of private keys and PKCS#12 with SECURITY_VIOLATION, the specific error codes (NOT_FOUND, PKI_ASN1_UNEXPECTED_TAG, PKI_API_MISUSE), and the token-frugal `verbosity`/`fields` options. One blemish: it says the tree is 'capped at 20000 nodes' while the schema documents maxNodes default 200000, a numeric inconsistency that could mislead.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is a single dense paragraph that packs roughly a dozen distinct behaviors into one block; the core purpose is front-loaded, but the error-code and option details run together with no structure. Nothing is wasted, yet the density makes it hard to scan for the one clause an agent needs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With 19 parameters, nested input object, an output schema, and full annotation coverage, the description covers the remaining gaps: input source exclusivity, artefact delivery modes, security refusals, and error semantics. An agent has everything needed to select and invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so 3 is the floor, but the description earns more by explaining that `nodePath` uses child indices and that each node's `path` is the value to pass, that `stringType`/`timeType` are required for IMPLICIT tags, and what `reencode.identical` means. The list of typed readers and their wire representations (bigint as decimal string, bytes as hex) adds meaning beyond the enum.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening clause gives a specific verb and resource — a read-only X.690 view of any DER/BER object producing a node tree — and immediately enumerates the three modes (tree listing, typed `read`, `reencode`). It distinguishes itself from siblings like encode_asn1 and explicitly routes private keys / PKCS#12 to inspect_private_key and inspect_pkcs12.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states conditions for each mode (`nodePath` narrows to a subtree, `sequence:true` for back-to-back values, `allowTrailingData` for stray bytes) and names the alternatives for refused inputs. What is missing is a positive statement of when to reach for this versus inspect_certificate or inspect_cms when the object is a known structure.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

decode_extensionDecode extension (one extnValue by OID, the 19 known kinds)A
Read-onlyIdempotent

Read-only decode of ONE X.509 extension value by its OID — the bytes INSIDE extnValue's OCTET STRING, as valueHex or value (DER by derBase64 / sandbox path) — exactly as inspect_certificate decodes a certificate's extensions: the 19 known kinds (basicConstraints, keyUsage, extendedKeyUsage, subjectAltName, nameConstraints, certificatePolicies, crlDistributionPoints, authorityInfoAccess, SCT list, …) come back with their members, any other OID as kind 'unknown' with its size; critical (default false) is reported and checked against the kind's RFC 5280 rule (a diagnostic, not a refusal). A value that is not well-formed for that OID is refused with PKI_X509_EXTENSION_MALFORMED (remedy: decode_asn1 for the plain tree). Token-frugal: verbosity:'summary', fields:[…]. The input is not a whole Extension SEQUENCE: pass the extnValue content, as inspect_certificate rawExtensions:true reports it in valueDer.

NameRequiredDescriptionDefault
oidYesThe extension OID (2.5.29.19 basicConstraints, 2.5.29.17 subjectAltName, …) — it selects the decoder.
valueNoThe extnValue content as DER bytes (derBase64, or a sandbox .der / .bin / .asn1 file): exactly one of pem / derBase64 / path. Exclusive with valueHex.
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
criticalNoThe criticality to report and to check against the kind (a kind RFC 5280 requires critical or non-critical gets a diagnostic).
valueHexNoThe extnValue content (the DER inside the OCTET STRING) as hex. Exclusive with value. Hexadecimal (an 0x prefix, colons and spaces are tolerated).
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der

Output Schema

NameRequiredDescription
oidNo
kindNoOne of the 19 decoded kinds, or unknown.
nameNoThe registry name of the OID.
decodedNoThe decoded members of the kind (cA / pathLenConstraint, usages, purposes, names, keyIdentifier, …); empty for an unknown OID.
criticalNo
sizeBytesNoSize of the value DER.
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish read-only, idempotent, non-destructive, closed-world behavior. The description adds real context beyond that: unknown OIDs return kind 'unknown' with size, criticality is checked against RFC 5280 but is only a diagnostic (not a refusal), malformed values yield PKI_X509_EXTENSION_MALFORMED, and verbosity:'summary'/fields give token-frugal output. It stops short of describing pagination/response shape, which the output schema covers.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The key distinction (what bytes to pass) is front-loaded and every sentence carries information. It is dense—long em-dash clauses with a lot packed into two sentences—which costs some readability, but nothing is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 10 parameters, nested value object, an output schema, and 22 security bounds delegated to the schema, the description covers the one pitfall that causes wrong calls (whole Extension SEQUENCE vs extnValue content) and the failure/remedy path. Nothing an agent needs to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so baseline is 3; the description adds meaning on top by clarifying that value/valueHex must carry the extnValue content rather than a full Extension SEQUENCE, that valueHex and value are exclusive, and that critical defaults to false and is diagnostic only. That is genuine semantic value beyond the schema text.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (decode), a precise resource (ONE X.509 extension value, the bytes inside extnValue's OCTET STRING), and scope via OID selection. It explicitly aligns itself with inspect_certificate's extension decoding and separates 'unknown' OID handling, so the agent can distinguish it from decode_asn1 and inspect_certificate without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Names the critical precondition ('The input is not a whole Extension SEQUENCE: pass the extnValue content, as inspect_certificate rawExtensions:true reports it in valueDer') and the alternative for malformed values ('remedy: decode_asn1 for the plain tree'). Both when-to-use and when-to-switch are explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

describe_engineDescribe engine & server capabilitiesA
Read-onlyIdempotent

Offline preflight (no input needed): server / engine / tool-API versions, whether this host's Web Crypto can verify, sign and decrypt (canVerify / canSign / canDecrypt) and satisfies the engine's Node floor, where randomness comes from, the engine's 22 default limits, the operator ceilings (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED), every server cap, the sandbox / cache / PKINATIVE_MCP_AT state and the extension allow-lists, the 57 PKI_* error codes (with class), 43 PKI_REASON_* and 97 PKI_DIAG_* codes, the wrapper codes, the purposes and key-usage names, the doctrine refusals (what this server will NOT do and the remedy) and the engine surface deliberately not exposed as inputs. network is always 'none'. Call it first in a session, or whenever a cap / ceiling refusal needs interpreting.

NameRequiredDescriptionDefault
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
verbosityNo'summary' keeps the scalar fields only (token-frugal).full

Output Schema

NameRequiredDescription
capsNo
engineNo
serverNo
networkNo
sandboxNo
ceilingsNo
refusalsNo
unexposedNo
randomnessNo
registriesNo
capabilitiesNo
defaultLimitsNo

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, destructiveHint=false, and openWorldHint=false. Beyond that, the description adds substantive behavior: it is offline and needs no input, network is always 'none', and it exposes sandbox/cache/PKINATIVE_MCP_AT state, operator ceilings, doctrine refusals, and intentionally unexposed engine surface.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The key framing, 'Offline preflight (no input needed)' and the first-call guidance, are front-loaded and the description is information-dense. However, it is a single enormous sentence bloated with exact inventory counts (22 limits, 57 PKI_* codes, 43 PKI_REASON_* codes, 97 PKI_DIAG_* codes) that do not help an agent select or invoke the tool and could be compressed.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so the description need not explain return values, and annotations cover the safety profile. The description still adds the offline/no-input behavior, the scope of exposed metadata, and the recommended first-call posture, making it complete for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so both optional parameters ('fields' and 'verbosity') are already documented in the schema, including dot-path behavior and summary/full semantics. The description adds the 'no input needed' framing but does not add parameter-specific meaning beyond the schema; baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: an offline preflight describing server, engine, and tool-API capabilities. It enumerates the capability domains (versions, Web Crypto, limits, error codes, refusals) clearly enough that an agent can distinguish it from sibling tools such as inspect_certificate or explain_code.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit invocation guidance: 'Call it first in a session, or whenever a cap / ceiling refusal needs interpreting.' That covers both the primary use and a diagnostic fallback, which is unusually clear routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

draft_governance_issueDraft a governance-compliant GitHub issue (HITL)A
Idempotent

Produce a LOCAL GitHub issue draft + compliance report for pkinative / pkinative-cli / pkinative-mcp and NEVER submit it — the agent drafts, a human reviews and files it under their own identity (Human-In-The-Loop contract, .github/AGENT_RULES.md). No GitHub write path and no network path exist in this server. The draft is checked against the zero-dependency + reproduction + no-secret policy; a violation (new runtime dependency, an embedded private key or password, missing reproduction, duplicateSearchPerformed:false) throws GOVERNANCE_VIOLATION. targetRepo 'pkinative' for engine behaviour (parsing, verification, limits, refusals), 'pkinative-cli' for the CLI, 'pkinative-mcp' for wrapper behaviour (schemas, sandbox, transport). Returns markdown inline (outputMode:'file' also writes a .md inside the sandbox). Use it only after reproducing a defect with TEST material. Present the draft AND the report to the user, then STOP.

NameRequiredDescriptionDefault
titleYesConcise issue title (imperative, no trailing period).
summaryYesOne or two paragraphs describing the problem or proposal. Never a private key or a password.
issueTypeYesIssue category: 'bug' | 'feature' | 'security' | 'docs' | 'performance'.
outputModeNo'inline' (default) returns the draft markdown in the response. 'file' additionally writes it to the sandbox (requires PKINATIVE_MCP_OUTPUT_DIR); outputPath must be a relative .md path.inline
outputPathNoRelative .md path inside the sandbox (only when outputMode='file').
targetRepoNoDestination repository label for the draft (documentation only — the server never contacts it). 'pkinative-mcp' (wrapper: schemas, sandbox, transport), 'pkinative' (engine: parsing, verification, limits, refusals) or 'pkinative-cli'.pkinative-mcp
reproductionYesThe minimal, locally-executed reproduction that justifies this issue (test material only).
actualBehaviorNoWhat actually happened (defaults to the reproduction result when omitted).
affectedPackagesNoPackages impacted by this issue (e.g. ["pkinative-mcp"], ["pkinative"]).
expectedBehaviorYesWhat you expected to happen instead.
duplicateSearchPerformedYesMUST be true: confirms you searched open AND closed issues/PRs for duplicates before drafting.

Output Schema

NameRequiredDescription
titleYes
filePathNoSandboxed absolute path (when outputMode='file').
warningsYes
issueTypeYes
sizeBytesYes
complianceYes
outputModeYes
targetRepoYes
draftMarkdownYesThe full draft, ready for a human to review and submit.

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations say non-read-only, non-destructive, idempotent, closed-world; the description goes further by naming the failure mode (GOVERNANCE_VIOLATION on new runtime dependency, embedded key/password, missing reproduction, duplicateSearchPerformed:false), the policy set checked, the HITL contract file, and the fact that no network or GitHub write path exists in the server. The 'file' output mode writing into the sandbox is consistent with readOnlyHint=false rather than contradicting it.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core contract (draft locally, never submit) and packed with useful specifics, with no filler sentences. It is dense and long-claused, and the targetRepo routing sentence restates what the enum descriptions already say, which costs a little.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an 11-parameter tool with nested reproduction and an output schema, the description covers scope, policy validation, error signaling, output modes and the HITL handoff. The one gap is what the accompanying compliance report actually contains, though the output schema likely covers that.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description earns more by linking parameters to behavior — duplicateSearchPerformed:false is a hard violation, outputMode:'file' writes a .md inside the sandbox, and targetRepo selects between engine, CLI and wrapper semantics. Most of this duplicates the schema text, but the enforcement consequences are genuinely additional.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Produce a LOCAL GitHub issue draft + compliance report') and immediately bounds the scope with 'NEVER submit it'. A reader knows exactly what artifact comes back and that no external write occurs, which no sibling tool in this PKI-oriented set does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives a clear precondition ('Use it only after reproducing a defect with TEST material') and a clear post-condition ('Present the draft AND the report to the user, then STOP'), plus the targetRepo routing rule for choosing among repos. No alternative tool is named, but none of the sibling tools overlaps this function, so the guidance is essentially complete.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

encode_asn1Encode ASN.1 (JSON node spec to DER)A
Idempotent

Encode a JSON node spec into DER through the engine's own encoders: spec is one node { type, … } — boolean (value), integer / enumerated (value: integer, decimal or 0x-hex string, negative allowed), null, bit-string (hex, unusedBits), named-bits (bits[]), octet-string (hex), oid / relative-oid (value), string (stringType utf8|numeric|printable|teletex|ia5|visible|universal|bmp, value), time (value: ISO instant or epoch ms; timeType UTCTime|GeneralizedTime|rfc5280 — the RFC 5280 rule by default), sequence / set / set-of (children[]; set-of is sorted as DER requires), explicit / implicit (tag, class universal|application|context|private, inner), tlv (class, tag, constructed, hex) and der (hex of one pre-encoded value, checked). Nesting is bounded by limits.maxDepth (PKI_LIMIT_EXCEEDED). The bytes are decoded back for the summary (root tag, node count). Output DER (default), hex (inline, ≤ 64 KiB) or PEM under label, inline as an embedded resource or as a sandbox file (never overwritten). Refuses a malformed node naming its JSON path ($.children[1].value …), a PEM encoding without label, a private-key label, a value that parses as a PKCS#8 key or a PKCS#12 file (SECURITY_VIOLATION — nothing here produces key material); a value the engine cannot encode is PKI_ASN1_* / PKI_OID_INVALID. Deterministic: the same spec gives the same bytes. The inverse of decode_asn1 (its tree uses the same vocabulary).

NameRequiredDescriptionDefault
specYesAn ASN.1 node spec: { type, … } with type one of boolean (value), integer / enumerated (value: integer or decimal / 0x-hex string), null, bit-string (hex, unusedBits), named-bits (bits[]), octet-string (hex), oid / relative-oid (value), string (stringType utf8|numeric|printable|teletex|ia5|visible|universal|bmp, value), time (value instant, timeType UTCTime|GeneralizedTime|rfc5280), sequence / set / set-of (children[] of nodes), explicit / implicit (tag, class universal|application|context|private, inner node), tlv (class, tag, constructed, hex), der (hex of one DER object). Nesting is bounded by limits.maxDepth.
labelNoThe PEM label for encoding:'pem' (required then; upper-case words and digits). A private-key label is refused.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
encodingNoArtefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'der'.der
outputModeNo'base64' (default) returns the artefact inline as an embedded resource (≤ 50 MiB). 'file' writes it inside PKINATIVE_MCP_OUTPUT_DIR (never overwrites; ≤ 1 GiB) and returns a resource_link.base64
outputPathNoRelative path inside the sandbox (only when outputMode='file'); the extension must match the encoding (.der .bin .asn1 for der; .pem for pem). Key-bearing extensions (.key .p8 .p12 .pfx) are refused: nothing here writes a private key.
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der

Output Schema

NameRequiredDescription
hexNoThe artefact as lowercase hex (when encoding='hex').
modeYes
sha256YesSHA-256 of the DER bytes (lowercase hex) — the reproducibility proof.
summaryYes
encodingYes
filePathNoSandboxed absolute path (when outputMode='file').
sizeBytesYesSize of the DER artefact.
diagnosticsYesNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
diagnosticCountsYesOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond annotations (idempotent, non-destructive), the description discloses nesting bounds (limits.maxDepth, PKI_LIMIT_EXCEEDED), output modes and size limits, security refusals (PKCS#8 key, PKCS#12, private-key labels, SECURITY_VIOLATION), error code families (PKI_ASN1_*, PKI_OID_INVALID), and determinism. This is rich context an agent could not infer from the annotations alone.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose and the decode_asn1 relationship are front-loaded, and every clause carries information. The single dense block is somewhat run-on, mixing node vocabulary, output modes, and error behavior without visual separation, but no sentence is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given an output schema exists, the description need not explain return values, and it covers the remaining complexity: encoding formats, sandbox file behavior, limit escalation, strict mode, PEM modes, and security refusals. Nothing an agent needs to call it safely is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents every parameter in detail. The description largely restates the node-type enumeration and fields, adding only small clarifications (negative integer values allowed, the rfc5280 time default) that are already present or implied in the schema. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise verb and resource ('Encode a JSON node spec into DER') and explicitly identifies itself as the inverse of the sibling decode_asn1. The node vocabulary is enumerated, so an agent knows exactly what this tool accepts before opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It clearly signals when to use this tool by naming the counterpart decode_asn1 and the shared node vocabulary. However, it does not explicitly exclude alternatives (e.g., encode_x509_structure) or state when not to use it, leaving a small inference gap.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

encode_x509_structureEncode X.509 structure (one building block to DER, from a JSON spec)A
Idempotent

Encode ONE X.509 building block to DER through pkinative's structural encoders, deterministically (the same spec gives the same bytes): structure is 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 or signature-algorithm; spec holds that structure's members (closed objects — an unknown or missing member is refused with VALIDATION_ERROR naming spec <path>; validity takes explicit notBefore AND notAfter, never an implicit now; a key identifier comes as hex or is computed from a certificate / CSR / PUBLIC KEY input). signature-algorithm takes signer instead of a spec (the key is imported NON-EXTRACTABLE and only its algorithm is read: ECDSA, Ed25519/Ed448, RSA with the REQUIRED rsaScheme — KEY_TYPE_REQUIRED for an encrypted key without keyType, SECRET_REQUIRED without a password). Output hex by default (structuredContent.hex, ≤ 64 KiB), or DER / PEM (PEM needs label), inline or as a sandbox file (never overwritten). The bytes are a fragment, not a signed object: nothing here signs. Use encode_asn1 for arbitrary ASN.1 and create_certificate / create_csr for whole objects.

NameRequiredDescriptionDefault
specNoThe structure's members (a closed object; an unknown member is refused with VALIDATION_ERROR naming it). name: a distinguished name — { "C": "FR", "O": "Acme", "CN": "x" } or an array of RDNs [[{ "type", "value", "stringType"? }], …]. name-attribute: { "type": "CN" | registry name | dotted OID, "value", "stringType"?: utf8|printable|ia5|numeric }. validity: { "notBefore", "notAfter" } — both ISO 8601 instants or epoch ms (required; no implicit now; whole seconds). spki: { "algorithm": dotted OID, "publicKey": hex of the BIT STRING content, "parameters"?: hex DER }. algorithm-identifier: { "oid", "parameters"?: hex DER }. attribute: { "oid", "values": [hex DER, …] }. extension: { "oid", "critical"?: false, "value": hex of the extnValue content }. extensions: the same closed object as create_certificate's spec.extensions (basicConstraints, keyUsage, extendedKeyUsage, subjectAltName, issuerAltName, subjectKeyIdentifier / authorityKeyIdentifier as hex, raw[]) — no key is at hand, so true for a key identifier is refused. basic-constraints: { "ca": boolean, "pathLen"?: n }. key-usage: ["digitalSignature", …] or { "usages": [...] }. extended-key-usage: ["serverAuth", "1.3.6.1.5.5.7.3.2", …] or { "purposes": [...] }. subject-alt-name: { "dns": [], "email": [], "uri": [], "ip": [], "registeredId": [], "directoryName": [<name>] }. subject-key-identifier: { "keyId": hex } or { "publicKey": <pem | derBase64 | path of a certificate, a CSR or a PUBLIC KEY> } (RFC 5280 §4.2.1.2 SHA-1 of the key bits). authority-key-identifier: { "keyId": hex } or { "issuerCertificate": <pem | derBase64 | path> } (its subjectKeyIdentifier, else the SHA-1 of its key bits). signature-algorithm takes no spec (signer decides it).
labelNoThe RFC 7468 label for encoding:'pem' (required then, refused otherwise); a key-bearing label is refused.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
signerNosignature-algorithm only: The signing key: exactly one of `key` (a PKCS#8 private key, plain or PBES2-encrypted, PEM or DER) or `p12` (a PKCS#12 file, PBES2 + PBMAC1 only). The key is imported into a NON-EXTRACTABLE Web Crypto key and never leaves the engine; no result ever carries a key byte. The algorithm follows the key: ECDSA on the key's curve, Ed25519/Ed448 (no hash), RSA REQUIRES rsaScheme ('pkcs1' | 'pss' — pkinative has no default). SHA-1 only with the tool's allowSha1:true. Nothing is signed here — only the AlgorithmIdentifier the key would write (RSA-PSS carries its digest and salt length).
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
encodingNoArtefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'hex'.hex
allowSha1NoAccept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write.
structureYesThe X.509 building block to encode.
outputModeNo'base64' (default) returns the artefact inline as an embedded resource (≤ 50 MiB). 'file' writes it inside PKINATIVE_MCP_OUTPUT_DIR (never overwrites; ≤ 1 GiB) and returns a resource_link.base64
outputPathNoRelative path inside the sandbox (only when outputMode='file'); the extension must match the encoding (.der .bin .asn1 for der; .pem for pem). Key-bearing extensions (.key .p8 .p12 .pfx) are refused: nothing here writes a private key.
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der

Output Schema

NameRequiredDescription
hexNoThe artefact as lowercase hex (when encoding='hex').
modeYes
sha256YesSHA-256 of the DER bytes (lowercase hex) — the reproducibility proof.
summaryYes
encodingYes
filePathNoSandboxed absolute path (when outputMode='file').
sizeBytesYesSize of the DER artefact.
diagnosticsYesNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
diagnosticCountsYesOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations: closed-object validation with VALIDATION_ERROR naming `spec <path>`, explicit notBefore/notAfter (never implicit now), the non-extractable key import with algorithm-only read, KEY_TYPE_REQUIRED / SECRET_REQUIRED failure modes, output default hex ≤64 KiB, file output never overwritten. Annotations only carry the side-effect flags, so this detail is substantive, not redundant.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with verb, resource and the determinism guarantee, and every clause carries real constraints. It is, however, a single dense run-on paragraph packing error codes, key handling and output modes together, which makes it slower to scan than a short bulleted structure would be.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 12-parameter, deeply nested tool with an output schema, this covers the remaining ground: output shapes (hex in structuredContent, DER/PEM inline or sandbox file), determinism, validation and crypto error codes, and the explicit non-goal of signing. Nothing an agent needs in order to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description earns a bump by explaining cross-parameter semantics the schema cannot, e.g. that signature-algorithm takes `signer` instead of `spec`, that PEM requires `label`, and that a key identifier is either hex or computed from a certificate / CSR / public key input. Per-structure member detail largely restates the already-rich `spec` schema field.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb (encode) plus resource (ONE X.509 building block to DER) with the deterministic guarantee stated. It enumerates the exact `structure` values and explicitly routes whole objects and arbitrary ASN.1 elsewhere, so it is distinguishable from every sibling without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit boundaries: 'Use encode_asn1 for arbitrary ASN.1 and create_certificate / create_csr for whole objects', plus 'The bytes are a fragment, not a signed object: nothing here signs'. It also names the within-tool alternative for signature-algorithm, which takes `signer` instead of a spec.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

explain_codeExplain a code (errors, reasons, diagnostics, wrapper codes)A
Read-onlyIdempotent

Explain any code this server can emit, from the registries vendored at the pkinative v1.0.0 tag: the 57 PKI_* engine error codes (coarse class, PkiError subclass, when raised, CWE, the engine's remedy and the MCP input that lifts it), the 43 PKI_REASON_* codes a verdict returns, the 97 PKI_DIAG_* diagnostics (with severity) and the wrapper's own codes (VALIDATION_ERROR, SECURITY_VIOLATION, LIMIT_CEILING_EXCEEDED, SECRET_REQUIRED, …). list:true enumerates (kind filters one vocabulary). Branch on codes, never on message text.

NameRequiredDescriptionDefault
codeNoThe code to explain (PKI_*, PKI_REASON_*, PKI_DIAG_* or a wrapper code such as SECURITY_VIOLATION). Omit with list:true to enumerate.
kindNoRestrict a listing to one vocabulary.
listNoList the codes (optionally of one kind) instead of explaining one.
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
verbosityNo'summary' keeps the scalar fields only (token-frugal).full

Output Schema

NameRequiredDescription
cweNo
codeNo
kindNo
codesNo
countNo
sinceNo
remedyNo
summaryNoWhen the code is raised / returned.
severityNo
standardNo
errorClassNoThe coarse class (usage, input, parse, security, limit, unsupported, crypto, password, check, internal).
engineClassNoThe PkiError subclass.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare this is a read-only, idempotent, non-destructive, closed-world call. The description adds useful behavioral context: which fields each code family explains (class, subclass, when raised, CWE, remedy, lifting input, severity) and that list mode enumerates rather than explains. It does not contradict the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, with the core action front-loaded and no filler. The long first sentence packs the code-family counts and returned details that an agent needs to understand scope, and the listing/branching guidance follows compactly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has an output schema and full parameter descriptions, so the description need not explain the return shape. It covers the code vocabularies, list mode, and filtering sufficiently for an agent to call it correctly; no critical behavior is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 100% schema description coverage for all five parameters, the schema already defines code, kind, list, fields, and verbosity. The description only repeats the list/kind behavior and adds no new syntax for fields or verbosity, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses the specific verb "Explain" on a precise resource, "any code this server can emit", and enumerates the four vocabularies: PKI_* errors, PKI_REASON_* codes, PKI_DIAG_* diagnostics, and wrapper codes. That is enough to separate it from sibling tools that inspect or create PKI artifacts.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It makes the primary use case clear and documents the listing path ("list:true enumerates") and kind filter, plus the operational rule "Branch on codes, never on message text." It does not explicitly name when to prefer a sibling such as describe_engine or state exclusions, so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

inspect_certificateInspect certificate (RFC 5280 report, extensions, fingerprints, signature check)A
Read-onlyIdempotent

Read-only RFC 5280 report of ONE certificate in one call: version, serial, signature algorithm, issuer and subject (RFC 4514 + RDNs), validity (epoch ms + ISO), public key (kind, bits, curve / modulus / exponent), every extension decoded (the 19 kinds pkinative knows; rawExtensions:true keeps them undecoded), self-issued flag, fingerprints (SHA-256 by default), and every engine diagnostic. extension:'subjectAltName' returns that one extension. issuer or checkSelfSignature:true adds a signature check — ONE signature against ONE key, which is NOT trust: verify_certificate_chain is the RFC 5280 §6 verdict (anchors, validity at an instant, names, policies, revocation). Token-frugal: verbosity:'summary', fields:[…]. Input: pem / derBase64 / path.

NameRequiredDescriptionDefault
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
issuerNoThe issuer certificate to check the signature against: exactly one of pem / derBase64 / path. Exclusive with checkSelfSignature. A valid signature is NOT trust: run verify_certificate_chain for that.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
allowSha1NoAccept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write.
extensionNoReturn only this decoded extension (NOT_FOUND when absent). Exclusive with rawExtensions.
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
certificateYesThe certificate (CERTIFICATE PEM or DER): exactly one of pem / derBase64 / path.
fingerprintsNoDigests of the DER to report.
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
rawExtensionsNoKeep every extension undecoded (kind raw, valueDer as hex) — the way past PKI_X509_EXTENSION_MALFORMED.
checkSelfSignatureNoCheck the certificate against its own key (a self-signed root). Exclusive with issuer.
requireAlgorithmMatchNoRequire the outer signatureAlgorithm to equal the TBS signature algorithm (RFC 5280 §4.1.1.2).

Output Schema

NameRequiredDescription
issuerNo
subjectNo
versionNo
notAfterNo
validityNo
extensionNoThe one extension asked for with `extension`.
notBeforeNo
publicKeyNo
signatureNoThe signature check asked for (issuer or self). ONE signature against ONE key — not a trust verdict.
sizeBytesNo
extensionsNo
selfIssuedNoissuer DER equals subject DER (self-issued; a self-SIGNATURE is a separate check).
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
fingerprintsNoColon-separated uppercase hex digests of the DER, by algorithm.
issuerStringNoRFC 4514 issuer.
serialNumberNo
subjectStringNoRFC 4514 subject.
extensionKindsNo
signatureValidNo
diagnosticCountNo
serialNumberHexNo
diagnosticCountsNoOccurrences per diagnostic code.
signatureAlgorithmNo
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.
subjectPublicKeyInfoNo

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, non-destructive, and closed-world behavior, and the description adds substantial context beyond them: one-call reporting, engine diagnostics, the rawExtensions escape hatch for malformed extensions, and an explicit warning that signature checking is not trust validation. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the report contents and remains dense with useful information throughout. It is a single long run-on paragraph, but nearly every clause earns its place and supports correct tool selection or invocation.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the rich schema, existing output schema, and annotations, the description is complete enough for correct invocation. It covers input modes, key optional behaviors, token-frugal options, diagnostics, and the critical distinction from chain verification.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3, but the description adds useful semantics for choosing extension, issuer, checkSelfSignature, verbosity, fields, and the pem/derBase64/path input modes. It does not fully explain every one of the 14 parameters, but it adds meaningful meaning beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: a read-only RFC 5280 report of ONE certificate in one call. It enumerates the reported fields and explicitly distinguishes itself from verify_certificate_chain, so an agent can identify it without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly says what this tool is not for: the signature check is ONE signature against ONE key and is NOT trust, and verify_certificate_chain is named as the RFC 5280 §6 verdict alternative. It also gives concrete when-to-use guidance for extension, issuer, checkSelfSignature, verbosity, and fields.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

inspect_cmsInspect CMS SignedData (RFC 5652 report, signers, attributes, signature values)A
Read-onlyIdempotent

Read-only RFC 5652 report of ONE CMS SignedData (.p7s / .p7m / .p7b, CMS or PKCS7 PEM, or a PDF /Contents): version, content type, attached content length (null = detached), digest algorithms, the embedded certificates (subject / issuer / serial), CRL and OCSP counts, and every signer — identifier (issuer + serial or key id), algorithms, signing time as CLAIMED, ESS signing-certificate, CMSAlgorithmProtection, signed and unsigned attributes (oid, name, value count), counter time-stamp count and the signature value as hex (what a counter time-stamp stamps: fields:['signerInfos.signature'], hash it, create_timestamp_request digestHex). signerCertificate (+ signerIndex, and content for a signer without signed attributes) adds signerSignature.valid — ONE signer against ONE key, a key check and NOT the verdict: verify_cms judges trust, digests, time-stamps and revocation. Trailing bytes are refused unless allowTrailingData:true; BER producers need encodingRules:'ber'. Refuses: NOT_FOUND for a signerIndex past the last signer, PKI_* for an unreadable structure. Token-frugal: verbosity:'summary', fields:[…].

NameRequiredDescriptionDefault
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
contentNoThe detached content, for a signer WITHOUT signed attributes (such a signer signs the content itself); ignored otherwise: exactly one of text / base64 / path.
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
allowSha1NoAccept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write.
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
signedDataYesThe CMS SignedData (CMS / PKCS7 PEM, or DER: .p7s .p7m .p7b): exactly one of pem / derBase64 / path.
signerIndexNoWhich signer the signature check targets (encoded order; NOT_FOUND past the last).
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
allowTrailingDataNoIgnore bytes after the ContentInfo (the zero padding of a PDF /Contents). Default false: trailing bytes are PKI_ASN1_TRAILING_DATA.
signerCertificateNoThe certificate to check ONE signer's signature against: exactly one of pem / derBase64 / path. A valid signature is a key check, NOT trust: verify_cms is the verdict.

Output Schema

NameRequiredDescription
signersNo
versionNo
crlCountNo
sizeBytesNo
contentTypeNo
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
signerCountNo
signerInfosNo
certificatesNoThe embedded certificates (a claim by whoever assembled the message).
contentLengthNoBytes of the encapsulated content; null when the signature is detached.
contentTypeOidNo
diagnosticCountNo
signerSignatureNoThe signature check asked for with signerCertificate: ONE signer against ONE key. A key check, not the verdict (verify_cms).
diagnosticCountsNoOccurrences per diagnostic code.
digestAlgorithmsNo
ocspResponseCountNo
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.
signerSignatureValidNo

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover read-only/idempotent safety, and the description adds real behavioral context on top: trailing bytes are refused by default, BER input needs an explicit flag, NOT_FOUND for an out-of-range signerIndex, PKI_* for unreadable structures, and the token-frugal verbosity/fields path. It doesn't discuss output shape or size limits on returned data, but with an output schema present that is forgivable.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the resource and the payload of what is reported, and every clause is substantive rather than filler. The single dense paragraph with nested parentheticals, embedded call syntax and field paths is hard to scan, so it stops short of full marks.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 12-parameter read tool with an output schema and annotations, nothing an agent needs is missing: input forms, report contents, the signature-check side path, error modes, encoding and trailing-data escape hatches, and token-frugality options. Return values are covered by the output schema, so the description doesn't need to repeat them.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3; the description still adds value by tying parameters to a workflow — fields:['signerInfos.signature'] for the counter time-stamp digest, verbosity:'summary', signerIndex pointing at one signer, content only for signers without signed attributes. It restates a fair amount of what the schema already says (allowTrailingData, encodingRules), which keeps it off a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names the exact operation (read-only RFC 5652 report of ONE CMS SignedData), enumerates the accepted input forms, and lists precisely what gets reported (signers, attributes, signature hex). It draws an explicit line against verify_cms, so an agent can distinguish it from the sibling without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Says when to reach for the signature check (signerCertificate + signerIndex, one signer, one key) and immediately states that this is not the verdict and that verify_cms judges trust, digests, time-stamps and revocation. It also names the triggering conditions for allowTrailingData (PDF /Contents padding) and encodingRules:'ber' (legacy producers), which is exactly the when-to-use guidance an agent needs.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

inspect_crlInspect CRL (RFC 5280 §5 report, one entry lookup, signature check)A
Read-onlyIdempotent

Read-only RFC 5280 §5 report of ONE certificate revocation list: version, issuer (RFC 4514 + RDNs), signature algorithm, thisUpdate / nextUpdate, CRL number, delta indicator and base CRL number, issuing distribution point, entry count (the entries are counted by walking, never all decoded — bounded by limits.maxRevokedCertificates), every extension decoded, size, diagnostics. find looks ONE serial up (serialHex, or a certificate whose serial and issuer are used) and returns entry (revoked, revocationDate, reason, invalidityDate, entry extensions). issuer adds a signature check — ONE signature against ONE key, not a verdict: check_crl_revocation judges a certificate against the list (freshness, scope, delta), verify_certificate_chain judges the whole chain. Token-frugal: verbosity:'summary' (issuerString, thisUpdateIso, nextUpdateIso, crlNumber, entryCount, isDelta, revoked, signatureValid). Input: pem / derBase64 / path.

NameRequiredDescriptionDefault
crlYesThe CRL (X509 CRL PEM or DER): exactly one of pem / derBase64 / path.
findNoLook one serial up in the list (the entries are walked, never all decoded): exactly one of serialHex / certificate.
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
issuerNoThe CRL issuer certificate to check the signature against: exactly one of pem / derBase64 / path. A valid signature is NOT trust: check_crl_revocation / verify_certificate_chain judge revocation.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
allowSha1NoAccept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write.
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der

Output Schema

NameRequiredDescription
entryNoThe lookup asked for with `find`: whether the serial is listed, and the entry when it is.
issuerNo
isDeltaNoCarries a deltaCRLIndicator (RFC 5280 §5.2.4).
revokedNo
versionNo
crlNumberNoDecimal (RFC 5280 §5.2.3); null when absent.
signatureNoThe signature check asked for with `issuer`. ONE signature against ONE key — not a trust verdict.
sizeBytesNo
entryCountNoEntries in revokedCertificates (counted by walking, bounded by limits.maxRevokedCertificates).
extensionsNo
nextUpdateNo
thisUpdateNo
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
issuerStringNoRFC 4514 issuer.
baseCrlNumberNoThe base CRL number a delta applies to (decimal); null otherwise.
nextUpdateIsoNo
thisUpdateIsoNo
signatureValidNo
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
signatureAlgorithmNo
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.
issuingDistributionPointNoRFC 5280 §5.2.5, decoded (fullName, onlyContainsUserCerts, onlyContainsCACerts, onlySomeReasons, indirectCRL, onlyContainsAttributeCerts); null when absent = the list covers everything its CA issued.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Adds non-obvious behavior well beyond the readOnly/idempotent annotations: the entry count comes from walking the list, never decoding all entries, and is bounded by limits.maxRevokedCertificates; `find` walks for exactly one serial; `issuer` performs a single-key signature check whose success is not a trust verdict. These are precisely the over-interpretation traps an agent would otherwise fall into.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and scope, then the per-parameter behaviors, then the token-frugal summary contract — every sentence carries information. The long enumeration of reported fields is useful but dense, and the closing 'Input: pem / derBase64 / path' partially repeats the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 10-parameter tool with nested inputs, enums and an output schema, the description covers input modes, the bounded-walk behavior, the single-key signature caveat, sibling routing, and the summary-mode field set. Return-value detail is legitimately delegated to the output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description goes beyond it by explaining `find` (one serial up, via serialHex or a certificate whose serial and issuer are used), what `issuer` consumes and returns, and exactly which fields verbosity:'summary' retains. It does not mention the `fields` dot-path projection or the `limits`/`strict`/`pemMode` knobs, so not fully additive.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource with scope ('Read-only RFC 5280 §5 report of ONE certificate revocation list') and enumerates exactly what is reported (version, issuer, signature algorithm, validity windows, CRL number, delta, IDP, entry count, extensions, diagnostics). The singular 'ONE' plus the named sibling comparison makes it distinguishable from check_crl_revocation and verify_certificate_chain at a glance.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly bounds what `issuer` does ('ONE signature against ONE key, not a verdict') and routes the agent elsewhere by name and condition: check_crl_revocation judges a certificate against the list on freshness/scope/delta, verify_certificate_chain judges the whole chain. That is an explicit when-to-use-this vs when-to-use-alternatives statement.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

inspect_csrInspect CSR (RFC 2986 report, requested extensions, proof of possession)A
Read-onlyIdempotent

Read-only RFC 2986 report of ONE PKCS#10 certification request: subject (RFC 4514 + RDNs), public key (kind, bits, curve / modulus), every attribute, the extensions requested through extensionRequest decoded as a certificate's would be (rawExtensions:true keeps them undecoded), whether a challengePassword is present (never its text), the signature algorithm, and — by default (verify, one Web Crypto verification) — the proof of possession: signature.valid tells whether the request was signed by the key it carries, with reasons[] (PKI_REASON_SIGNATURE_NOT_CHECKED for SHA-1 without allowSha1:true). That is NOT trust: no CA signed a CSR, and a CA is free to ignore every extension it asks for. Pair with create_certificate (subjectPublicKey = the request) to issue from it. Token-frugal: verbosity:'summary' (subject, key kind, extension kinds, signatureValid), fields:[…]. Input: pem / derBase64 / path (.csr .p10 .der .pem).

NameRequiredDescriptionDefault
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
verifyNoVerify the request's signature under the key it carries (proof of possession; one Web Crypto verification). false skips it.
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
requestYesThe PKCS#10 request (CERTIFICATE REQUEST PEM or DER): exactly one of pem / derBase64 / path.
allowSha1NoAccept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write.
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
rawExtensionsNoKeep every requested extension undecoded (kind raw, valueDer as hex) — the way past PKI_X509_EXTENSION_MALFORMED.

Output Schema

NameRequiredDescription
subjectNo
versionNov1 (encoded 0), the only version RFC 2986 defines.
publicKeyNo
signatureNoThe proof of possession (verify:true): the signature under the key the request carries. Not trust — a CSR is unsigned by any CA.
sizeBytesNo
attributesNoEvery attribute in encoded order (extensionRequest, challengePassword, …).
extensionsNoThe extensions requested through extensionRequest (RFC 2985 §5.4.2), decoded; null when the request asks for none. A CA is free to ignore them.
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
subjectStringNoRFC 4514 subject.
extensionKindsNo
signatureValidNo
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
challengePasswordNoWhether a challengePassword attribute is present (its text is attacker-controlled and never reported).
signatureAlgorithmNo
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.
subjectPublicKeyInfoNo

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare read-only/idempotent/non-destructive, but the description adds material context beyond them: verify runs exactly one Web Crypto verification, signature.valid means signed-by-carried-key, PKI_REASON_SIGNATURE_NOT_CHECKED is emitted for SHA-1 without allowSha1, challengePassword text is never returned, and CSR extensions carry no CA authority. Those are precisely the behavioral caveats an agent needs and none are in the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The definition is front-loaded with the report contents and ends with the token-frugal options and input forms, so scanning order is sound. The first sentence is a long comma-chained enumeration that runs on, and a few clauses (the RFC 4514 / modulus detail) could be trimmed without loss.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 10-parameter, nested-input inspection tool with an output schema, the description covers everything an agent must know before calling: acceptable input encodings, PEM mode, default verification cost, degradation paths, and the trust caveat. Return-value shape is left to the output schema, which is the correct division of labor.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description earns above that by defining verbosity:'summary' contents (subject, key kind, extension kinds, signatureValid), the rawExtensions escape hatch, the three request input forms with typical file extensions, and the verify default. It does not touch limits, strict, pemMode, encodingRules, or allowSha1 semantics, so it is not exhaustive.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Read-only RFC 2986 report of ONE PKCS#10 certification request', then enumerates exactly what is reported (subject, public key, attributes, requested extensions, challengePassword presence, signature algorithm, proof of possession). That precision separates it from siblings like inspect_certificate and decode_asn1 without requiring the schema to be opened.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly tells the agent when to reach for which mode: verbosity:'summary' for token-frugal calls, rawExtensions:true when PKI_X509_EXTENSION_MALFORMED blocks decoding, verify:false to skip proof of possession, and it names the follow-on sibling 'Pair with create_certificate (subjectPublicKey = the request) to issue from it'. It also warns what this tool is NOT (trust) so the agent does not use it as a substitute for chain validation.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

inspect_ocsp_responseInspect OCSP response (RFC 6960 report, signature check)A
Read-onlyIdempotent

Read-only RFC 6960 §4.2 report of ONE OCSP response (the DER a responder returned): the response status (only successful carries an answer), the BasicOCSPResponse — responder id (by name or by key hash), producedAt, every SingleResponse (CertID: hash algorithm, issuer name / key hashes, serial; status good / revoked with time and reason / unknown; thisUpdate, nextUpdate, extensions), response extensions, the embedded certificates, the signature algorithm — whether an RFC 8954 nonce is echoed, and diagnostics (bounded by limits.maxOcspSingleResponses). responder adds a signature check against that certificate; omitted, the embedded certificates are tried and the one that verifies is named. ONE signature against ONE key is NOT a status verdict: check_ocsp_status matches the CertID, judges the responder's authorisation (RFC 6960 §4.2.2.2), freshness and nonce. Token-frugal: verbosity:'summary' (status, responses[serialNumber, status], noncePresent, signatureValid). Input: derBase64 / path (or PEM).

NameRequiredDescriptionDefault
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
responseYesThe OCSP response (DER, as the responder returned it): exactly one of pem / derBase64 / path.
responderNoThe responder certificate to check the signature against: exactly one of pem / derBase64 / path. Omitted: the certificates embedded in the response are tried (signature.source embedded). A valid signature is NOT authorisation: check_ocsp_status judges the responder (RFC 6960 §4.2.2.2).
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der

Output Schema

NameRequiredDescription
statusNoOCSPResponseStatus (RFC 6960 §4.2.1). Only successful carries a signed answer.
responsesNo
signatureNoThe signature check: against `responder`, or the first embedded certificate whose key verifies. ONE signature against ONE key — authorisation is judged by check_ocsp_status.
sizeBytesNo
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
noncePresentNoA nonce extension is present on the response.
basicResponseNoThe BasicOCSPResponse; null when the status is not successful.
signatureValidNo
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/closed-world. The description adds substantive behavior: signature checking against an explicit responder or the embedded certificates, the caveat that a valid signature is not authorisation, and the bounded `maxOcspSingleResponses` limit. It does not discuss error modes, but `strict` and diagnostic behavior are documented in the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose and the sibling distinction. It is dense and long, but every clause carries information relevant to calling the tool correctly; no filler or tautology.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given a complex tool with 8 parameters and a nested schema, the description covers what is returned, the signature-check semantics, token-frugal mode, and the critical distinction from the sibling status-checking tool. An output schema exists, so return-value detail is appropriately delegated.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and already documents all parameters, so the baseline is 3. The description adds useful semantics beyond the schema: the exactly-one-of derBase64/path/PEM input shape, the `responder` omission fallback, and the exact content of `verbosity:'summary'`.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Read-only RFC 6960 §4.2 report of ONE OCSP response') and enumerates exactly what is reported (response status, BasicOCSPResponse fields, SingleResponse details, nonce echo, signature algorithm). It explicitly distinguishes itself from sibling `check_ocsp_status` by noting that a signature check is not a status verdict.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Names the alternative tool and the condition that selects it: `check_ocsp_status` matches the CertID and judges responder authorisation, freshness and nonce. It also states the omitted-`responder` fallback behavior, giving the agent a clear when-to-use narrative.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

inspect_pkcs12Inspect PKCS#12 (MAC kind, SafeContents, bags; optional MAC check and opening, no key byte)A
Read-onlyIdempotent

Describe a PKCS#12 (.p12 / .pfx) WITHOUT its password: version, the MAC construction (kind 'pbmac1' RFC 9579 with its PBKDF2 / HMAC parameters, or 'pkcs12-kdf' — the RFC 7292 Appendix B MAC pkinative never verifies; null when the file has none), every SafeContents with its encryption scheme (PBES2 parameters, or the name of a legacy RC2 / 3DES scheme pkinative refuses), and the bags of the unencrypted SafeContents. With password (or PKINATIVE_MCP_PASSWORD): macVerified (true / false — a wrong password or an altered file is a REPORT, not an error; null without a MAC) and opened: every SafeContents opened, each bag as an allow-list view — a certBag by subject / issuer / serial, a keyBag or pkcs8ShroudedKeyBag by its algorithm, curve and encryption parameters, a crlBag by size. No result ever carries a key byte, a bag value or the password, not even from an unencrypted keyBag. Refusals: a wrong password on an ENCRYPTED SafeContents is PKI_CRYPTO_DECRYPTION_FAILED; a legacy MAC PKI_KEY_MAC_UNSUPPORTED and a legacy cipher PKI_KEY_ENCRYPTION_UNSUPPORTED (only when a password asks for them), each with the OpenSSL 3.4 conversion remedy; a public-key integrity-mode file PKI_KEY_MAC_UNSUPPORTED at parse. Nothing is imported here: open_pkcs12 is the one-call verdict with non-extractable key import. limits.maxPkcs12Bags and maxPkcs12KdfIterations bound the file's declared cost. Token-frugal: verbosity:'summary' (macKind, macVerified, contentCount, bagCount, bagKinds).

NameRequiredDescriptionDefault
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
pkcs12YesThe PKCS#12 file (.p12 / .pfx; DER, or BER with encodingRules:'ber' for a Windows-written PFX): exactly one of pem / derBase64 / path.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
passwordNoThe password of an encrypted PKCS#8 key or a PKCS#12 file (UTF-8). Never echoed, logged or cached. Alternatively set PKINATIVE_MCP_PASSWORD in the server environment and omit this input (never both) — the variable keeps the secret out of the model transcript.
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der

Output Schema

NameRequiredDescription
macNoThe MacData, or null when the file carries none (its integrity cannot be verified).
openedNoWith password: every SafeContents opened (the encrypted ones decrypted), its bags as allow-list views.
macKindNo
versionNo
bagCountNoBags listed: those of `opened` when a password was given, else those of the plain SafeContents.
bagKindsNo
contentsNoThe AuthenticatedSafe entries, in encoded order.
sizeBytesNo
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
macVerifiedNoWith password: true when the PBMAC1 MAC matches, false for a wrong password or an altered file (a report, not an error); null when the file has no MAC.
contentCountNo
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, but the description adds critical behavior beyond them: no key byte or bag value is ever returned, a wrong password on a MAC is a report rather than an error, refusals map to specific PKI_* codes, and limits bound declared cost. This is unusually rich behavioral disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose and no-key-byte constraint are front-loaded, and the density is justified for a complex tool. However, the long parenthetical clauses and multi-error-code listing make some sentences run on, slightly reducing readability despite high information density.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given an output schema exists, the description need not explain return values, yet it still covers both modes, refusal codes, limits, and the summary mode. Nothing material is missing for an agent to select and call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema baseline is 3, but the description adds meaning: password can come from PKINATIVE_MCP_PASSWORD instead, verbosity:'summary' returns a named set of scalar fields, and limits.maxPkcs12Bags / maxPkcs12KdfIterations bound cost. It does not fully re-explain the nested pkcs12 input shape or encodingRules beyond brief context.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Describe a PKCS#12 (.p12 / .pfx)') and enumerates exactly what is reported: version, MAC construction, SafeContents encryption, bags. It explicitly distinguishes itself from open_pkcs12 by noting it never imports and that open_pkcs12 is the one-call verdict with non-extractable key import.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Clearly partitions usage into password-less inspection versus opening with `password` or PKINATIVE_MCP_PASSWORD, and specifies the alternative open_pkcs12 for import. It also states when legacy errors occur, and the summary verbosity option, leaving little to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

inspect_private_keyInspect private key (PKCS#8 description, optional import proof, no key byte)A
Read-onlyIdempotent

Describe a PKCS#8 private key WITHOUT its password: format ('pkcs8' or 'encrypted-pkcs8'), version, kind (ec / rsa / rsa-pss / ed25519 / ed448 / x25519 / x448 / ml-dsa / unknown), curve, algorithm OID and name, attributes, whether a public key is embedded; for an encrypted key the PBES2 parameters (PBKDF2 iterations, PRF, AES key size, salt length, ciphertext size) or the name of a scheme pkinative refuses (PKCS#12 / PKCS#5 v1.5 ciphers). tryImport:true additionally imports the key into a NON-EXTRACTABLE Web Crypto key and reports the handle (type, algorithm, usages) — an encrypted key then needs password (or PKINATIVE_MCP_PASSWORD; SECRET_REQUIRED) and keyType (KEY_TYPE_REQUIRED), an RSA key needs rsaScheme (RSA_SCHEME_REQUIRED, no default); a wrong password is PKI_CRYPTO_DECRYPTION_FAILED and a legacy cipher PKI_KEY_ENCRYPTION_UNSUPPORTED with the openssl pkcs8 remedy. No result ever carries a key byte, a scalar or the password: the report is an allow-list view and the key never leaves the engine (nothing exports or writes a key here). A PUBLIC KEY or a non-PKCS#8 DER is refused with the PKI_* cause. A call carrying a password is never cached. Token-frugal: verbosity:'summary' (format, kind, curve, encrypted, scheme, imported). Input: pem / derBase64 / path. inspect_pkcs12 is its PKCS#12 counterpart; use it before sign_cms / create_csr to learn what a signer needs (keyType, rsaScheme).

NameRequiredDescriptionDefault
keyYesThe PKCS#8 private key (PRIVATE KEY or ENCRYPTED PRIVATE KEY PEM, or DER): exactly one of pem / derBase64 / path. A SEC1 / PKCS#1 key must be converted first (openssl pkcs8 -topk8).
hashNoSignature hash of the imported key. Default: the curve's hash for ECDSA (SHA-256/384/512), SHA-256 for RSA; refused for Ed25519 / Ed448. 'SHA-1' needs allowSha1:true. With tryImport:true only.
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
keyTypeNoThe type of an ENCRYPTED key (it reveals its type only once decrypted). With tryImport:true only; KEY_TYPE_REQUIRED when missing for an encrypted key.
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
passwordNoThe password of an encrypted PKCS#8 key or a PKCS#12 file (UTF-8). Never echoed, logged or cached. Alternatively set PKINATIVE_MCP_PASSWORD in the server environment and omit this input (never both) — the variable keeps the secret out of the model transcript.
allowSha1NoAccept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write.
rsaSchemeNoREQUIRED to import an RSA key: 'pkcs1' (RSASSA-PKCS1-v1_5) or 'pss' (RSA-PSS); pkinative has no default (RSA_SCHEME_REQUIRED). With tryImport:true only.
tryImportNoAlso import the key into a NON-EXTRACTABLE Web Crypto key and describe the handle (never exported). An encrypted key then needs password and keyType; an RSA key needs rsaScheme.
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
saltLengthNoRSA-PSS salt length in bytes (rsaScheme:'pss' only; default: the hash length). With tryImport:true only.
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der

Output Schema

NameRequiredDescription
kindNo
curveNo
formatNo
schemeNoThe password-based encryption scheme of an encrypted key.
versionNo
importedNoThe import proof (full), or its ok flag (summary).
algorithmNo
encryptedNotrue for an ENCRYPTED PRIVATE KEY (format encrypted-pkcs8).
publicKeyNo
sizeBytesNoSize of the DER structure (ciphertext included for an encrypted key).
attributesNo
encryptionNo
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
algorithmNameNoThe name of the key algorithm OID (ecPublicKey, rsaEncryption, Ed25519 …), plain keys only.
encryptedBytesNo
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnly/idempotent annotations, it discloses important behavior: no result carries a key byte, scalar, or password, password-bearing calls are not cached, and tryImport creates a non-extractable Web Crypto handle without exporting anything. It also gives concrete error causes for wrong passwords, legacy ciphers, and non-PKCS#8 input.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose and primary constraints are front-loaded, and the dense wording is largely justified by the tool's complexity. Still, the single long paragraph is harder to scan than it needs to be for 14 parameters and could benefit from tighter grouping.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the 14-parameter nested schema and the presence of an output schema, the description is complete for correct invocation. It covers input formats, import preconditions, password handling, security guarantees, error mappings, and the relevant sibling tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, so the schema itself documents all parameters. The description still adds valuable conditional meaning for tryImport, password, keyType, rsaScheme, and verbosity:'summary', though it does not similarly elaborate on every parameter such as limits, strict, or pemMode.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb and resource: 'Describe a PKCS#8 private key WITHOUT its password'. It enumerates exactly what is reported and explicitly distinguishes the tool from inspect_pkcs12, its PKCS#12 counterpart.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It says to use this before sign_cms or create_csr to learn what a signer needs, and names inspect_pkcs12 as the alternate tool for PKCS#12. It also states key preconditions: only PKCS#8 is accepted, and SEC1/PKCS#1 keys must be converted first.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

inspect_timestampInspect time-stamp (RFC 3161 response, token or TSTInfo)A
Read-onlyIdempotent

Read-only RFC 3161 report of a TimeStampResp (.tsr: status, statusStrings, failInfo, and the token when granted), a TimeStampToken (.tst) or a bare TSTInfo — auto-detected in that order unless as names one. The TSTInfo view: policy, message imprint (algorithm + hex), serial, genTime (epoch ms + ISO), accuracy (null = undeclared), ordering, nonce (decimal), the TSA name as STATED (a hint, not an identity), extensions; the token view adds its CMS envelope (signer count, embedded certificates). Parsing proves nothing — verify_timestamp is the verdict (imprint, TSA signature, chain, revocation). Refuses with the PKI_* cause when the bytes are none of the three (as:'…' shows one reading's exact refusal); strict:true escalates the first engine warning (OpenSSL tokens carry PKI_DIAG_CMS_SET_NOT_SORTED). Token-frugal: verbosity:'summary' (status, genTime, serial, imprint).

NameRequiredDescriptionDefault
asNoRead the input as this structure. Omitted: auto-detected in the order response, token, tstinfo (the result's `as` says which).
inputYesThe time-stamp object: a TimeStampResp (.tsr), a TimeStampToken (.tst) or a bare TSTInfo (DER, or any PEM block): exactly one of pem / derBase64 / path.
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der

Output Schema

NameRequiredDescription
asNoWhat the input was read as.
tokenNoThe token (of a granted response, or the input itself): what it asserts and its CMS envelope.
statusNoThe PKIStatusInfo of a response. Only granted / grantedWithMods carry a token.
genTimeNo
tstInfoNoThe bare TSTInfo (as:'tstinfo').
sizeBytesNo
imprintHexNo
statusCodeNo
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
serialNumberNo
hashAlgorithmNo
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, yet the description still adds real behavior: the PKI_* refusal when bytes match none of the three structures, strict:true escalating the first engine warning (with the concrete PKI_DIAG_CMS_SET_NOT_SORTED case for OpenSSL tokens), and the warning that the TSA name is 'as STATED (a hint, not an identity)'. It also flags that accuracy null means undeclared, which prevents a false-negative read.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the capability and the structure order, then verification routing, then failure modes, then the frugality hint — a sensible priority order. The prose is dense and em-dash heavy, with the TSTInfo field list packed into one long clause, but no sentence is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an 8-parameter inspection tool with a full output schema and safety annotations, the description covers everything an agent needs: accepted input forms, auto-detection, per-view contents, failure signalling, and the hand-off to verify_timestamp. Return-value detail is legitimately left to the output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds meaning beyond it: it clarifies what 'summary' actually keeps (status, genTime, serial, imprint) rather than the schema's vague 'scalar fields only', and reinforces that `as` overrides auto-detection and shows one reading's exact refusal. fields, limits, pemMode and encodingRules are left entirely to the schema, which is adequate given full coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: a read-only RFC 3161 report over TimeStampResp, TimeStampToken, or bare TSTInfo, with the auto-detection order spelled out. It enumerates what each view surfaces (policy, imprint, serial, genTime, accuracy, nonce, TSA name, CMS envelope), so an agent knows exactly what it gets without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes the agent: 'Parsing proves nothing — verify_timestamp is the verdict (imprint, TSA signature, chain, revocation)', naming the sibling and the condition that selects it. It also gives a token-frugal mode (verbosity:'summary') and the `as` override for ambiguous inputs, so both when-to-use and how-to-scope are covered.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

open_pkcs12Open PKCS#12 (MAC, decryption, non-extractable key import, certificates; optional PEM bundle)A
Idempotent

Open a PKCS#12 (.p12 / .pfx) in one call with password (or PKINATIVE_MCP_PASSWORD; SECRET_REQUIRED without one): the RFC 9579 PBMAC1 MAC is verified, every PBES2 SafeContents decrypted, every key unwrapped by the host straight into a NON-EXTRACTABLE Web Crypto key — described (algorithm, handle type, usages) and never exported — every certificate parsed (compact refs) and every CRL counted. The verdict is valid + integrity ('verified' | 'unverified' | 'mismatch') + reasons[]; a negative verdict is a REPORT, never isError: a wrong password or altered file → integrity 'mismatch' + PKI_REASON_PKCS12_MAC_MISMATCH (nothing decrypted); a legacy RC2 / 3DES file → PKI_REASON_PKCS12_ENCRYPTION_UNSUPPORTED (convert it once with openssl pkcs12 -legacy then -export -pbmac1_pbkdf2); no MAC or a legacy MAC → PKI_REASON_PKCS12_INTEGRITY_UNVERIFIED unless allowUnverifiedIntegrity:true (trusted origin only); an RSA key without rsaScheme ('pkcs1' | 'pss', hash default SHA-256; no default scheme) → PKI_REASON_PKCS12_RSA_SCHEME_UNSPECIFIED with that key's signingKey null. exportCertificates:true attaches a PEM bundle of the certificates then the CRLs (PEM only; inline embedded resource or a sandbox .pem file, never overwritten) — only from a file that opened; nothing is exported otherwise, and never a key. No result carries a key byte, a bag value or the password; a call with a password is never cached. Token-frugal: verbosity:'summary' (valid, integrity, reasonCodes, counts). Prefer inspect_pkcs12 first: it describes the file (MAC kind, bags) without any password.

NameRequiredDescriptionDefault
hashNoThe hash of the RSA signature algorithm (with rsaScheme only). Default 'SHA-256'.
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
pkcs12YesThe PKCS#12 file (.p12 / .pfx; DER, or BER with encodingRules:'ber' for a Windows-written PFX): exactly one of pem / derBase64 / path.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
encodingNoArtefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'pem'.pem
passwordNoThe password of an encrypted PKCS#8 key or a PKCS#12 file (UTF-8). Never echoed, logged or cached. Alternatively set PKINATIVE_MCP_PASSWORD in the server environment and omit this input (never both) — the variable keeps the secret out of the model transcript.
rsaSchemeNoWhat an RSA key will sign with: 'pkcs1' (RSASSA-PKCS1-v1_5) or 'pss' (RSA-PSS). No default: without it an RSA key is not imported and the verdict says PKI_REASON_PKCS12_RSA_SCHEME_UNSPECIFIED. Ignored by keys of every other kind.
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
outputModeNo'base64' (default) returns the artefact inline as an embedded resource (≤ 50 MiB). 'file' writes it inside PKINATIVE_MCP_OUTPUT_DIR (never overwrites; ≤ 1 GiB) and returns a resource_link.base64
outputPathNoRelative path inside the sandbox (only when outputMode='file'); the extension must match the encoding (.pem only — a bundle has no DER form). Key-bearing extensions (.key .p8 .p12 .pfx) are refused: nothing here writes a private key.
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
exportCertificatesNoAttach a PEM bundle of the certificates (CERTIFICATE blocks) then the CRLs (X509 CRL blocks) as the `artefact` — only from a file that opened (valid:true); never a key.
allowUnverifiedIntegrityNoAccept a file whose integrity cannot be checked (no MAC, or the legacy RFC 7292 Appendix B MAC) and report it valid — only for a file whose origin you trust: without a MAC any unencrypted bag can be swapped, and a file with nothing encrypted then opens under any password. The report says integrity:'unverified' either way.

Output Schema

NameRequiredDescription
keysNoEvery private key bag, in encoded order: where it was, its names, the certificate sharing its localKeyId, and the non-extractable Web Crypto handle described (never exported).
validNoEverything opened and the integrity verified (or explicitly waived). false is a negative verdict (reasons[]), never an error.
reasonsNoWhy the verdict is negative (empty when valid): the engine PKI_REASON_* codes, each with the standard it cites and the path of the offending element. A reason is returned, never thrown.
artefactNoWith exportCertificates:true on a file that opened: the PEM bundle (certificates then CRLs), delivered once as the embedded resource (text) or the resource_link; summary counts the blocks.
crlCountNo
keyCountNo
integrityNo'verified' for a matching PBMAC1 MAC; 'unverified' when it could not be checked (no MAC, legacy MAC); 'mismatch' for a wrong password or an altered file — nothing was decrypted then.
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
reasonCodesNo
certificatesNoEvery certificate that parsed, in encoded order — the keys' own and any chain the file carries.
exportSkippedNoexportCertificates was asked but the file did not open (valid:false): nothing was exported.
diagnosticCountNo
certificateCountNo
diagnosticCountsNoOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations: discloses that keys enter the host as non-extractable and are never exported, that no result contains key bytes or the password, that calls with a password are never cached, and that a negative verdict is a report rather than isError. It maps each failure mode (wrong password, legacy RC2/3DES, missing MAC, unspecified RSA scheme) to a specific reason code, which annotations cannot express.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core operation and verdict semantics, and nearly every sentence carries a distinct behavioral fact. However, the single long run-on paragraph is dense, and rsaScheme/allowUnverifiedIntegrity/exportCertificates semantics are largely repeated from the schema descriptions, so a little trimming is possible.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 15-parameter tool with a nested file object, an output schema, and non-obvious state effects (non-extractable key import), the description covers password sourcing, integrity-verification outcomes, export restrictions, caching, and the absence of key material in results. An agent can decide whether, how, and with what caveats to call it without further reading.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds consequence-level meaning: rsaScheme has no default and an RSA key without it yields PKI_REASON_PKCS12_RSA_SCHEME_UNSPECIFIED with signingKey null; a password may come from PKINATIVE_MCP_PASSWORD instead; verbosity 'summary' keeps only scalars. Some parameters (limits, fields, pemMode, encodingRules) are not discussed in the description, leaving the schema to carry them.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (open) and resource (PKCS#12 .p12/.pfx), and enumerates the concrete work performed: MAC verification, PBES2 decryption, non-extractable key unwrap, certificate parsing, CRL counting. It also names the sibling inspect_pkcs12 and the difference between them, so the agent can distinguish the two without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says 'Prefer inspect_pkcs12 first' with the reason (it describes MAC kind and bags without a password), and gives conditional guidance for allowUnverifiedIntegrity (trusted origin only) and exportCertificates (only from a file that opened). When/when-not conditions are spelled out rather than implied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

resolve_oidResolve OID (name, validate, list, encode, decode)A
Read-onlyIdempotent

Object identifiers in one call. mode:'name' → the registered name of each dotted OID (null when unregistered; ≤ 1000 per call); 'validate' → X.660 validity of each (at least two arcs, first arc 0–2, second ≤ 39 under 0 and 1, no leading zeros — reported, never an error); 'list' → the engine's registry (oid, name, standard), filtered by a case-insensitive substring of the name or the OID, paged with offset / limit (default 200, ≤ 2000); 'encode' → the X.690 bytes of each OID as hex, form 'content' (base-128 content octets, default) or 'tlv' (tag + length + content), relative:true for a RELATIVE-OID (tlv only); 'decode' → the dotted OID of hex octets (content, or a whole TLV with form:'tlv') with its name. Refuses: a member outside its mode, relative without form:'tlv' (VALIDATION_ERROR); an invalid OID in encode (PKI_OID_INVALID — validate reports it instead); malformed octets in decode (PKI_OID_INVALID, PKI_ASN1_*). Token-frugal: verbosity:'summary', fields:[…].

NameRequiredDescriptionDefault
hexNodecode: the octets to decode (content octets, or the whole TLV with form:'tlv'). Hexadecimal (an 0x prefix, colons and spaces are tolerated).
formNoencode / decode: 'content' (the base-128 content octets only) or 'tlv' (tag + length + content, i.e. the whole DER value).content
modeYes'name' (registered names), 'validate' (X.660 validity), 'list' (the registry, paged), 'encode' (dotted → hex), 'decode' (hex → dotted + name).
oidsNoname / validate / encode: the dotted OIDs (≤ 1000).
inputNodecode: the encoded OBJECT IDENTIFIER as a whole DER TLV (or any PEM block holding one): exactly one of pem / derBase64 / path. Exclusive with hex; implies form:'tlv'.
limitNolist: page size (≤ 2000).
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
filterNolist: case-insensitive substring of the name or the dotted OID.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
offsetNolist: first entry returned.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
relativeNoencode / decode: a RELATIVE-OID (X.690 §8.20, universal tag 13) instead of an OBJECT IDENTIFIER — needs form:'tlv' (the engine encodes a relative OID as a whole TLV only).
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der

Output Schema

NameRequiredDescription
oidNodecode: the dotted OID.
modeNo
nameNodecode: the registered name (null when unregistered or relative).
oidsNoname: { oid, name|null }; validate: { oid, valid }; encode: { oid, hex }.
countNoNumber of OIDs / entries in the result.
limitNo
totalNolist: entries matching the filter.
offsetNo
entriesNolist: the page of registry entries.
hasMoreNo
allValidNovalidate: true when every OID is valid.
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the readOnly/idempotent annotations: it names the refusal conditions and concrete error codes (VALIDATION_ERROR, PKI_OID_INVALID, PKI_ASN1_*), states that validate reports rather than errors, caps batches at 1000, and gives paging defaults. That is real behavioral context an agent cannot get from the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the mode map using a consistent mode:'x' → … pattern, followed by refusals and the token-frugal hint; nearly every clause carries information. It is a single very dense block with some restatement of schema enum values (form, mode), which costs a little scannability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Rich annotations plus an output schema mean the description needn't explain return shapes, and it covers modes, errors and paging well. The notable gap against a 15-parameter tool is that the nested input object (pem/derBase64/path as an alternative decode source) and the limits/strict/pemMode knobs are never mentioned in the prose.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds cross-parameter meaning: which parameters bind to which mode, the content-vs-TLV distinction, the ≤1000/≤2000 caps, the case-insensitive substring filter, and the rule that relative requires form:'tlv'. It stops short of mentioning the decode-via-input path (pem/derBase64/path).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Each of the five modes is given a specific input→output statement ('name' → registered name per OID, 'encode' → X.690 hex bytes, 'decode' → dotted OID plus name), so the agent knows exactly what the tool does in every branch. The OID-specific framing also implicitly separates it from the generic ASN.1 siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description routes within the tool ('an invalid OID in encode … validate reports it instead') and states the null-when-unregistered condition for 'name', which is genuine when-to-use guidance. It doesn't compare against siblings like decode_asn1 or encode_asn1, so the inter-tool choice is left implicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sign_cmsSign CMS (RFC 5652 SignedData, attached or detached, Web Crypto signed)A

Produce an RFC 5652 SignedData over content (attached, or detached:true) or over a precomputed contentDigest (detached only — the PDF /ByteRange case), with ONE signer: signer (a PKCS#8 key or a PKCS#12 file, imported as a NON-EXTRACTABLE Web Crypto key — nothing generates or exports a key here) and certificate (required with signer.key; a PKCS#12 bag's certificate serves otherwise), chain and crls parsed (never embedded unread) then embedded. Signed attributes are always written: contentType, messageDigest, signingTime (default PKINATIVE_MCP_AT or now, whole seconds; 'none' omits it — pin it for reproducible bytes), SigningCertificateV2 and CMSAlgorithmProtection (on by default; CAdES / PAdES need them), plus your signedAttributes / unsignedAttributes (DER Attributes as hex). sid defaults to issuerAndSerialNumber (keep it unless the reader is known). The new signature is verified against the signer certificate BEFORE it is returned (KEY_MISMATCH otherwise). RSA keys need signer.rsaScheme; SHA-1 needs allowSha1:true. Output DER (default; .p7s), PEM (label CMS) or hex, inline or as a sandbox file (never overwritten). Add a counter time-stamp afterwards with amend_cms.

NameRequiredDescriptionDefault
sidNoHow the SignerInfo names the certificate. Keep issuerAndSerialNumber unless the reader is known: subjectKeyIdentifier (version 3) is refused by some deployed readers and needs the certificate's SKI extension.issuerAndSerialNumber
crlsNoCRLs to embed (DER or X509 CRL PEM) (every PEM block of every item is taken).
chainNoFurther certificates to embed (the chain) (every PEM block of every item is taken). The signer certificate is always embedded once; a PKCS#12 bag's other certificates are added too.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
signerYesThe signing key: exactly one of `key` (a PKCS#8 private key, plain or PBES2-encrypted, PEM or DER) or `p12` (a PKCS#12 file, PBES2 + PBMAC1 only). The key is imported into a NON-EXTRACTABLE Web Crypto key and never leaves the engine; no result ever carries a key byte. The algorithm follows the key: ECDSA on the key's curve, Ed25519/Ed448 (no hash), RSA REQUIRES rsaScheme ('pkcs1' | 'pss' — pkinative has no default). SHA-1 only with the tool's allowSha1:true.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
contentNoThe content to sign (hashed with the signer's digest; embedded unless detached): exactly one of text / base64 / path.
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
detachedNoOmit the content from the SignedData (the signature travels without what it signs; most S/MIME and every PDF signature).
encodingNoArtefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'der'.der
allowSha1NoAccept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write.
outputModeNo'base64' (default) returns the artefact inline as an embedded resource (≤ 50 MiB). 'file' writes it inside PKINATIVE_MCP_OUTPUT_DIR (never overwrites; ≤ 1 GiB) and returns a resource_link.base64
outputPathNoRelative path inside the sandbox (only when outputMode='file'); the extension must match the encoding (.p7s .p7m .p7b .cms .der for der; .pem for pem). Key-bearing extensions (.key .p8 .p12 .pfx) are refused: nothing here writes a private key.
certificateNoThe signer certificate: exactly one of pem / derBase64 / path. Required with signer.key; optional with signer.p12 (the bag's certificate is used, and this one wins when both are given).
contentTypeNoThe eContentType (dotted OID). Default id-data (1.2.840.113549.1.7.1); any other makes a version 3 SignedData.
signingTimeNoThe claimed signing time: an ISO 8601 instant, epoch milliseconds, 'now', or 'none' to omit the attribute (PAdES forbids it, S/MIME expects it). Whole seconds. Omitted: PKINATIVE_MCP_AT when pinned, else the wall clock — pin it for reproducible bytes.
contentDigestNoThe content's digest, already computed with the signer's digest algorithm (the PDF /ByteRange case). Requires detached:true. Exclusive with content. Hexadecimal (an 0x prefix, colons and spaces are tolerated).
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
signedAttributesNoFurther signed attributes, each one DER Attribute as hex, written verbatim. A type the builder writes itself, a repeated type or a countersignature is refused (PKI_API_MISUSE).
unsignedAttributesNoUnsigned attributes, each one DER Attribute as hex (a time-stamp token already in hand: amend_cms add-timestamp checks the imprint first). A signed-only type is refused.
algorithmProtectionNoWrite CMSAlgorithmProtection (RFC 6211, RFC 8933 §4).
signingCertificateV2NoWrite the ESS SigningCertificateV2 attribute (RFC 5035), binding the signature to this certificate — CAdES, PAdES and RFC 3161 require it.

Output Schema

NameRequiredDescription
hexNoThe artefact as lowercase hex (when encoding='hex').
modeYes
sha256YesSHA-256 of the DER bytes (lowercase hex) — the reproducibility proof.
summaryYes
encodingYes
filePathNoSandboxed absolute path (when outputMode='file').
sizeBytesYesSize of the DER artefact.
diagnosticsYesNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
diagnosticCountsYesOccurrences per diagnostic code.
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the sparse annotations (readOnlyHint=false, idempotentHint=false) the description discloses the traits that actually matter for a signing tool: the key is imported as a non-extractable Web Crypto key, nothing generates or exports a key, no result ever carries a key byte, chain/crls are parsed before embedding, the signature is verified against the signer certificate before return (KEY_MISMATCH), and sandbox files are never overwritten.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Roughly 200 words for a 22-parameter tool, front-loaded with the operation and its modes before descending into key handling and output. It is dense but nearly every clause carries a constraint; the one cost is that the middle sentences are so compressed that an agent may have to re-read them to separate content, digest and attribute rules.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a high-complexity signing tool the description covers the entry rules (content vs contentDigest, key vs p12, certificate requirement), the invariants (always-written signed attributes, verification before return), and the exit paths (der/pem/hex, inline base64 or sandbox file, then amend_cms). An output schema exists, so return shape need not be described.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, so the baseline is 3; the description still adds cross-parameter meaning the schema's per-field docs do not — RSA keys require signer.rsaScheme, SHA-1 requires allowSha1, signingTime defaults to PKINATIVE_MCP_AT and should be pinned for reproducible bytes, sid's default and its reader-compatibility risk. Signed/unsigned attribute types and the certificate-vs-p12 precedence are clarified as well.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence states a specific verb and resource ('Produce an RFC 5652 SignedData') plus the three input modes (content attached, content detached, contentDigest detached-only), and closes by routing counter-timestamping to amend_cms. An agent can distinguish this from verify_cms, inspect_cms and amend_cms without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives concrete selection rules: detached for S/MIME and every PDF signature, contentDigest for the PDF /ByteRange case (detached only), 'keep issuerAndSerialNumber unless the reader is known', SHA-1 only with allowSha1. What's missing is an explicit statement of when NOT to use it (e.g. 'to check an existing signature use verify_cms'), though amend_cms is correctly named as the follow-up.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

validate_certificate_pathValidate certificate path (RFC 5280 §6.1 on an ordered path, link signatures)A
Read-onlyIdempotent

RFC 5280 §6.1 validation of a path YOU ordered (path, leaf first, then each issuer) against YOUR trustAnchors at at: validity, basic constraints, key usage, name constraints and the policy inputs. By default every link signature is verified first — certificate i against i+1, the last against the anchor whose subject matches its issuer — and reported per link in signatures[] (PKI_REASON_SIGNATURE_INVALID fails the verdict). checkSignatures:false gives a structural verdict: valid stays false (PKI_REASON_SIGNATURE_NOT_CHECKED on every link) and failingReasons carries what actually fails; signaturesChecked says which one you got. No revocation here (verify_certificate_chain judges CRL / OCSP evidence; check_crl_revocation / check_ocsp_status judge one certificate). A negative verdict is valid:false + reasons[], never isError. Pin at for a reproducible, cacheable verdict. SHA-1 signatures need allowSha1:true. Token-frugal: verbosity:'summary'.

NameRequiredDescriptionDefault
atNoThe instant the verdict is evaluated at. An ISO 8601 date-time (UTC unless zoned; e.g. 2027-01-01T00:00:00Z), epoch milliseconds as digits, or 'now'. Omitted: PKINATIVE_MCP_AT when the operator pinned it, else the wall clock (a clock-dependent result is never cached).
pathYesThe path to validate, leaf FIRST, then each issuer up to (but not necessarily including) the anchor (every PEM block of every item is taken). Every PEM block of every item is taken, in order.
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
policyNoRFC 5280 §6.1.1 policy inputs.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
allowSha1NoAccept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write.
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
trustAnchorsYesTrust anchors (root certificates you trust) (every PEM block of every item is taken). At least one is required: a verdict is relative to the anchors you name.
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
checkSignaturesNoVerify every link signature before validating (certificate i against i+1, the last against the anchor whose subject is its issuer). false = structural verdict only: PKI_REASON_SIGNATURE_NOT_CHECKED stays in reasons[], signaturesChecked:false.

Output Schema

NameRequiredDescription
pathNoThe path as validated, leaf first, up to (and including) the trust anchor it reached.
validNoThe engine verdict. With checkSignatures:false it is always false (every link is PKI_REASON_SIGNATURE_NOT_CHECKED): read failingReasons for the structural verdict.
reasonsNoWhy the verdict is negative (empty when valid): the engine PKI_REASON_* codes, each with the standard it cites and the path of the offending element. A reason is returned, never thrown.
pathLengthNo
signaturesNoOne row per certificate of the input path: the issuer it was verified against (the next certificate, or the matching anchor) and the verdict; not-checked when no issuer was found or checkSignatures is false.
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
evaluatedAtNoThe ISO instant used — re-pass it as `at` to reproduce the verdict.
reasonCodesNo
failingReasonsNoThe reasons that fail THIS call: reasons[] as is when the signatures were checked, minus the PKI_REASON_SIGNATURE_NOT_CHECKED markers otherwise. Empty = the path validates (structurally, when signaturesChecked is false).
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
evaluatedAtSourceNo
signatureVerdictsNo
signaturesCheckedNo
failingReasonCodesNo
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare a safe read, but the description adds substantial behavioral detail: default signature verification and per-link reporting, the structural-only behavior when checkSignatures is false, signaturesChecked, PKI_REASON_SIGNATURE_INVALID, negative verdicts returning valid:false + reasons rather than isError, SHA-1 gating, and cache behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is dense but front-loaded and every sentence carries operational meaning. Some content repeats or expands on already-rich schema descriptions, but none of it is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex 12-parameter validation tool with an output schema and annotations, the description covers the important behaviors an agent needs: path ordering, anchors, signature verification, failure reporting, no revocation, cache semantics, and SHA-1 handling.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description still adds meaningful semantics beyond the schema: path ordering and issuer inclusion, pinning `at` for caching/reproducibility, and the distinction between structural and signature-checking modes.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific standard, action, and scope: RFC 5280 §6.1 validation of a caller-ordered path against named trust anchors at a specific instant. It explicitly differentiates from sibling verify_certificate_chain and names its role relative to CRL/OCSP tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit when-to-use guidance, including that revocation is out of scope and that verify_certificate_chain / check_crl_revocation / check_ocsp_status handle those concerns. It also explains the checkSignatures:false mode and recommends pinning `at` for reproducible, cacheable verdicts.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

verify_certificate_chainVerify certificate chain (the RFC 5280 §6 verdict, offline revocation)A
Read-onlyIdempotent

The one-call trust verdict for a certificate: builds a path from leaf through candidates to one of YOUR trustAnchors, verifies every signature, validates the path at at (RFC 5280 §6: validity, basic constraints, key usage, name constraints, policies — policy inputs), checks serverName (RFC 6125 host or IP) and purposes (extended key usage along the path), and judges revocation from the CRL / OCSP bytes YOU supply (crls, ocspResponses; requireRevocation:true demands evidence for every certificate below the anchor; this server never fetches). A negative verdict is valid:false + reasons[] (PKI_REASON_* codes) in structuredContent — never isError; isError is reserved for inputs the engine refuses. Pin at (ISO 8601) for a reproducible, cacheable verdict; evaluatedAt echoes the instant used. Token-frugal: verbosity:'summary' (valid, reasonCodes, pathLength). Prefer it over inspect_certificate.signature for any trust question; build_certificate_path / validate_certificate_path explain a failing path.

NameRequiredDescriptionDefault
atNoThe instant the verdict is evaluated at. An ISO 8601 date-time (UTC unless zoned; e.g. 2027-01-01T00:00:00Z), epoch milliseconds as digits, or 'now'. Omitted: PKINATIVE_MCP_AT when the operator pinned it, else the wall clock (a clock-dependent result is never cached).
crlsNoCRLs (DER or X509 CRL PEM) for offline revocation (every PEM block of every item is taken). Optional; this server never fetches a CRL — pass the bytes you obtained.
leafYesThe end-entity certificate to judge: exactly one of pem / derBase64 / path.
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
policyNoRFC 5280 §6.1.1 policy inputs.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
purposesNoExtended key usages the leaf must allow: serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, ocspSigning, any, or a dotted OID.
allowSha1NoAccept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write.
ocspNonceNoThe nonce you put in the OCSP request (1–32 bytes), to be matched in the responses. Hexadecimal (an 0x prefix, colons and spaces are tolerated).
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
candidatesNoUntrusted intermediate certificates the path may use (every PEM block of every item is taken). Optional; the certificates embedded in a CMS message are also candidates.
serverNameNoThe identity the leaf must be valid for (RFC 6125): exactly one of host / ip.
trustAnchorsYesTrust anchors (root certificates you trust) (every PEM block of every item is taken). At least one is required: a verdict is relative to the anchors you name.
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
ocspResponsesNoOCSP responses (DER) for offline revocation (every PEM block of every item is taken). Optional; this server never queries a responder — pass the bytes you obtained.
requireOcspNonceNoRefuse an OCSP response that does not echo the nonce.
requireRevocationNoRequire fresh revocation evidence for the end-entity certificate (PKI_REASON_REVOCATION_UNKNOWN otherwise — the engine demands it for the leaf; an intermediate is judged from the evidence you pass); default false = revocation is checked when evidence is given.

Output Schema

NameRequiredDescription
pathNoThe path found, leaf first, up to (and including) the trust anchor.
validNoThe verdict. false is a negative verdict (reasons[]), never an error.
reasonsNoWhy the verdict is negative (empty when valid): the engine PKI_REASON_* codes, each with the standard it cites and the path of the offending element. A reason is returned, never thrown.
exploredNoCandidate paths explored (bounded by limits.maxPathsExplored).
pathLengthNo
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
evaluatedAtNoThe ISO instant the verdict was evaluated at — re-pass it as `at` to reproduce the verdict.
reasonCodesNo
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
evaluatedAtSourceNo
revocationEvidenceNo
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.
signatureVerificationsNo

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, non-destructive, closed-world), yet the description adds non-obvious behavior: negative verdicts return valid:false + PKI_REASON_* reasons in structuredContent and are never isError, isError is reserved for engine-refused inputs, and a verdict is only cached when `at` is pinned. These are exactly the things an agent cannot infer from annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core verdict statement and with no filler sentences; every clause maps to a real behavior (path build, signature verify, RFC 5280 validation, name check, revocation, caching, verbosity). It is dense to the point of being a single long run-on sentence with heavy parenthetical nesting, which costs a little skimmability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 19-parameter tool with nested input unions, an output schema, and a complex trust model, the description covers the whole decision surface an agent needs: what is verified, what must be supplied versus fetched, how failures are surfaced, how to get deterministic/cacheable results, and the token-frugal mode. Return-value detail is correctly left to the output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description earns above that by framing `policy` as RFC 5280 §6.1.1 inputs, `serverName` as an RFC 6125 host-or-IP, `purposes` as EKU along the path, and `requireRevocation:true` as demanding evidence for every certificate below the anchor. Minor friction: the schema text for `requireRevocation` scopes the demand to the leaf and judges intermediates from supplied evidence, which reads slightly differently from the description's blanket phrasing.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('one-call trust verdict for a certificate') and enumerates the distinct sub-operations it performs: path building, signature verification, RFC 5280 §6 validation, RFC 6125 name check, EKU check, and revocation judging. It also explicitly contrasts itself with siblings ('Prefer it over inspect_certificate.signature for any trust question; build_certificate_path / validate_certificate_path explain a failing path'), so an agent can place it among the 30+ siblings without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit selection rule against the nearest alternatives ('prefer it over inspect_certificate.signature for any trust question') and assigns the diagnostic role to build_certificate_path / validate_certificate_path. It also states the scope constraint that makes it usable: 'this server never fetches' — the caller must supply CRL/OCSP bytes.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

verify_cmsVerify CMS SignedData (the RFC 5652 verdict: signers, chains, time-stamps, revocation)A
Read-onlyIdempotent

The one-call verdict on a CMS SignedData (a .p7s, an S/MIME body, a PDF /Contents): for EVERY signer, the signed attributes (contentType, messageDigest, ESS signing-certificate, CMSAlgorithmProtection), the content digest (attached, or content / contentDigest for a detached signature — a detached signature without its content is NOT valid), the signature, the certificate's chain through candidates and the embedded certificates to one of YOUR trustAnchors at at (RFC 5280 §6, purposes along the path), every counter time-stamp (RFC 3161, verified like verify_timestamp), and revocation from the CRL / OCSP bytes YOU supply plus those the message carries (requireRevocation:true demands evidence; this server never fetches). atTimeStamp:true is long-term validation: a signer's chain is judged at its verified time-stamp instead of at. requireSigningCertificate / requireAlgorithmProtection enforce the CAdES / RFC 8933 profile. A negative verdict is valid:false + reasons[] (PKI_REASON_* codes, per signer too; intact tells unaltered from untrusted) — never isError; isError is for inputs the engine refuses (content given for an attached message, both content and contentDigest). Pin at for a reproducible, cacheable verdict. Token-frugal: verbosity:'summary' (valid, reasonCodes, per-signer codes).

NameRequiredDescriptionDefault
atNoThe instant the verdict is evaluated at. An ISO 8601 date-time (UTC unless zoned; e.g. 2027-01-01T00:00:00Z), epoch milliseconds as digits, or 'now'. Omitted: PKINATIVE_MCP_AT when the operator pinned it, else the wall clock (a clock-dependent result is never cached).
crlsNoCRLs (DER or X509 CRL PEM) for offline revocation, added to those the message carries. Optional; this server never fetches a CRL.
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
contentNoThe detached content (required for a detached signature; refused for an attached one): exactly one of text / base64 / path.
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
purposesNoExtended key usages every signer's chain must permit (emailProtection for S/MIME): serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, ocspSigning, any, or a dotted OID.
allowSha1NoAccept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write.
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
candidatesNoUntrusted intermediate certificates the path may use (every PEM block of every item is taken). Optional; the certificates embedded in a CMS message are also candidates.
signedDataYesThe CMS SignedData to judge (CMS / PKCS7 PEM, or DER: .p7s .p7m): exactly one of pem / derBase64 / path.
atTimeStampNoJudge each signer's chain at the time its own VERIFIED counter time-stamp proves (long-term validation) instead of `at`; a signer without a valid time-stamp is still judged at `at`, and the TSA's chain always is.
trustAnchorsYesTrust anchors (root certificates you trust) (every PEM block of every item is taken). At least one is required: a verdict is relative to the anchors you name.
contentDigestNoThe detached content's digest (computed with the signer's digest algorithm) instead of the content — the PDF /ByteRange case. Exclusive with content. Hexadecimal (an 0x prefix, colons and spaces are tolerated).
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
ocspResponsesNoOCSP responses (DER), added to those the message carries. Optional; this server never queries a responder.
allowTrailingDataNoIgnore bytes after the ContentInfo (the zero padding of a PDF /Contents). Default false: trailing bytes are PKI_ASN1_TRAILING_DATA.
requireRevocationNoRequire fresh revocation evidence for the end-entity certificate (PKI_REASON_REVOCATION_UNKNOWN otherwise — the engine demands it for the leaf; an intermediate is judged from the evidence you pass); default false = revocation is checked when evidence is given.
requireSigningCertificateNoRequire every signer to name its certificate in an ESS signing-certificate attribute (CAdES / PAdES).
requireAlgorithmProtectionNoRequire every signer to carry CMSAlgorithmProtection (RFC 6211).

Output Schema

NameRequiredDescription
validNoAt least one signer and EVERY signer valid. false is a negative verdict (reasons[]), never an error.
reasonsNoWhy the verdict is negative (empty when valid): the engine PKI_REASON_* codes, each with the standard it cites and the path of the offending element. A reason is returned, never thrown.
signersNoOne verdict per signer, in encoded order.
atTimeStampNo
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
evaluatedAtNoThe ISO instant the chains were judged at — re-pass it as `at` to reproduce the verdict.
reasonCodesNo
signerCountNo
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
evaluatedAtSourceNo
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.
signatureVerificationsNoSignature verifications performed, chains and time-stamps included.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, but the description adds far more: a negative verdict is valid:false with reasons, never an isError; isError is reserved for refused inputs (content supplied for an attached message, both content and contentDigest); the server never fetches CRLs or OCSP; and cacheability depends on pinning `at`. That is exactly the behavioral context an agent cannot infer elsewhere.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core verdict statement, and every sentence carries distinct information despite the density. It is a single long paragraph with heavy parenthetical nesting, which for a 21-parameter tool is defensible but not maximally scannable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return structure need not be described, and the description still covers the semantics of the verdict, error conditions, offline revocation posture, and caching. Nothing an agent needs in order to invoke this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description still adds cross-parameter meaning the schema cannot express — that a detached signature without its content is invalid, that `contentDigest` covers the PDF /ByteRange case, that `atTimeStamp` judges each signer at its own verified TSA time, and that `intact` separates altered from untrusted. It does not touch all 21 parameters, so not a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource — 'the one-call verdict on a CMS SignedData' — and enumerates exactly what the verdict covers (signed attributes, digests, signature, chain, time-stamps, revocation). The framing as a verdict tool cleanly separates it from inspection siblings like inspect_cms, which only decode.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives strong conditional guidance: pin `at` for reproducible verdicts, use `atTimeStamp` for long-term validation, `requireRevocation` to demand evidence, `content`/`contentDigest` for detached signatures, `verbosity:'summary'` for token-frugal calls. It never explicitly names when to prefer a sibling such as inspect_cms or verify_certificate_chain instead, so it stops short of full alternative routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

verify_timestampVerify time-stamp (the RFC 3161 verdict: imprint, TSA signature, chain, revocation)A
Read-onlyIdempotent

The one-call verdict on an RFC 3161 time-stamp: exactly one of token (a TimeStampToken, .tst) or response (the whole TimeStampResp, .tsr — a declined response is PKI_REASON_TSP_NOT_GRANTED), judged against what YOU say was stamped — request (the .tsq you sent: imprint, nonce and policy are all checked, the only way to catch a replay), data (hashed with the token's own algorithm) or digestHex; at least one is required, because a token verified without what it stamps proves nothing (VALIDATION_ERROR otherwise). The TSA's signature is verified, its certificate must chain to one of YOUR trustAnchors (through candidates and the certificates the token carries) at at with a critical timeStamping EKU (allowNonCriticalTimeStampingEku:true for deployed TSAs that are not), and revocation is judged from the CRL / OCSP bytes YOU supply (requireRevocation:true demands evidence). A negative verdict is valid:false + reasons[] (PKI_REASON_* codes) — never isError. genTime / earliest / latest (accuracy applied) are established only when valid. Pin at for a reproducible, cacheable verdict. Token-frugal: verbosity:'summary'.

NameRequiredDescriptionDefault
atNoThe instant the verdict is evaluated at. An ISO 8601 date-time (UTC unless zoned; e.g. 2027-01-01T00:00:00Z), epoch milliseconds as digits, or 'now'. Omitted: PKINATIVE_MCP_AT when the operator pinned it, else the wall clock (a clock-dependent result is never cached).
crlsNoCRLs (DER or X509 CRL PEM) for offline revocation (every PEM block of every item is taken). Optional; this server never fetches a CRL — pass the bytes you obtained.
dataNoThe data that was stamped (hashed with the token's own imprint algorithm): exactly one of text / base64 / path.
tokenNoThe TimeStampToken (a ContentInfo over a TSTInfo; .tst): exactly one of pem / derBase64 / path. Exclusive with response.
fieldsNoDot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields.
limitsNoRaise pkinative's 22 named security bounds for this call (trusted input only; absent keys keep the engine defaults). A value above an operator ceiling (PKINATIVE_MCP_MAX_INPUT_BYTES, PKINATIVE_MCP_MAX_KDF_ITERATIONS, PKINATIVE_MCP_MAX_PATHS_EXPLORED) is LIMIT_CEILING_EXCEEDED; describe_engine lists the defaults and the ceilings.
strictNoEscalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[].
pemModeNo'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers).strict
requestNoThe TimeStampReq you sent (.tsq): exactly one of pem / derBase64 / path. The strongest statement of what was stamped: imprint, nonce and policy are all checked; the only way to catch a replayed response.
responseNoThe whole TimeStampResp the TSA returned (.tsr): exactly one of pem / derBase64 / path. Exclusive with token. A response that granted nothing is PKI_REASON_TSP_NOT_GRANTED.
allowSha1NoAccept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write.
digestHexNoThe digest that was stamped, when you hold that rather than the data. Exclusive with data. Hexadecimal (an 0x prefix, colons and spaces are tolerated).
verbosityNo'summary' keeps the scalar fields only (token-frugal).full
candidatesNoUntrusted certificates the TSA's path may use (every PEM block of every item is taken). Optional; the certificates the token carries are also candidates.
trustAnchorsYesTrust anchors (root certificates you trust) (every PEM block of every item is taken). At least one is required: a verdict is relative to the anchors you name.
encodingRulesNo'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write).der
ocspResponsesNoOCSP responses (DER) for offline revocation (every PEM block of every item is taken). Optional; this server never queries a responder — pass the bytes you obtained.
requireRevocationNoRequire fresh revocation evidence for the end-entity certificate (PKI_REASON_REVOCATION_UNKNOWN otherwise — the engine demands it for the leaf; an intermediate is judged from the evidence you pass); default false = revocation is checked when evidence is given.
allowNonCriticalTimeStampingEkuNoAccept a TSA whose extKeyUsage names timeStamping but is not critical (RFC 3161 §2.3 requires it critical; some deployed TSAs are not).

Output Schema

NameRequiredDescription
tsaNoThe TSA certificate whose key verified the token, or null.
chainNoThe chain judged by verify_certificate_chain, or null when there was no certificate to judge.
validNoWhether the token is evidence that the expected hash existed at genTime. false is a negative verdict (reasons[]), never an error.
latestNogenTime plus the declared accuracy — the instant an LTV check compares against.
genTimeNoThe time the TSA asserts (ISO 8601) — established only when valid.
reasonsNoWhy the verdict is negative (empty when valid): the engine PKI_REASON_* codes, each with the standard it cites and the path of the offending element. A reason is returned, never thrown.
earliestNogenTime minus the declared accuracy.
diagnosticsNoNon-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one).
evaluatedAtNoThe ISO instant the TSA chain was judged at — re-pass it as `at` to reproduce the verdict.
reasonCodesNo
diagnosticCountNo
diagnosticCountsNoOccurrences per diagnostic code.
evaluatedAtSourceNo
diagnosticsTruncatedNotrue when distinct diagnostics beyond the 200 listed were dropped.
signatureVerificationsNo

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, but the description adds substantial context beyond them: a failed verification returns valid:false + PKI_REASON_* reasons and never isError, the server never fetches CRLs or queries a responder (offline-only), requireRevocation is enforced for the leaf, and clock-dependent results are never cached. It also discloses the timeStamping EKU criticality rule and the escape hatch for deployed TSAs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded, but the body is a single dense paragraph of em-dash-chained clauses that an agent must parse in one pass. Every fact it contains is relevant, yet the mental model (inputs → checks → verdict → caching) would land faster as separate sentences or a short list.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 19-parameter tool with nested objects and an output schema, the description covers what an agent still needs: mandatory inputs, the exclusivity rules, what is verified, what error codes a negative verdict carries, and how caching interacts with `at`. Nothing material to calling it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema carries the per-field burden and a 3 would be the baseline. The description still adds relational semantics the schema alone doesn't make obvious: at least one of token/response is required, at least one of request/data/digestHex is required, trustAnchors are mandatory because 'a verdict is relative to the anchors you name', and data is hashed with the token's own algorithm.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening clause states a specific verb and resource: 'the one-call verdict on an RFC 3161 time-stamp', with the checks (imprint, TSA signature, chain, revocation) named up front. It distinguishes itself from decoding siblings by framing the output as a verdict (valid:false + reasons[]) rather than an inspection, so an agent can tell verify_timestamp from inspect_timestamp without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives strong mode-selection guidance: token vs response exclusivity, and why `request` beats `data` ('the only way to catch a replay'), plus 'pin `at` for a reproducible, cacheable verdict' and 'verbosity: summary' for token economy. It never explicitly says when to reach for inspect_timestamp or create_timestamp_request instead, so it stops short of the 5 bar.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 34 tool updatesv0.1.0
    • First observedamend_cms
    • First observedbuild_certificate_path
    • First observedcheck_certificate_name
    • First observedcheck_certificate_purpose
    • First observedcheck_crl_revocation
    • First observedcheck_ocsp_status
    • First observedcompute_fingerprint
    • First observedconvert_pem
    • First observedcreate_certificate
    • First observedcreate_csr
    • First observedcreate_ocsp_request
    • First observedcreate_timestamp_request
    • First observeddecode_asn1
    • First observeddecode_extension
    • First observeddescribe_engine
    • First observeddraft_governance_issue
    • First observedencode_asn1
    • First observedencode_x509_structure
    • First observedexplain_code
    • First observedinspect_certificate
    • First observedinspect_cms
    • First observedinspect_crl
    • First observedinspect_csr
    • First observedinspect_ocsp_response
    • First observedinspect_pkcs12
    • First observedinspect_private_key
    • First observedinspect_timestamp
    • First observedopen_pkcs12
    • First observedresolve_oid
    • First observedsign_cms
    • First observedvalidate_certificate_path
    • First observedverify_certificate_chain
    • First observedverify_cms
    • First observedverify_timestamp

TDQS

A4.3/5.0

Scored across 34 tools

Disambiguation4/5

The tools split into clear families — inspect (facts), verify/check (verdicts), create/sign, and encode/decode — and descriptions repeatedly point to the correct alternative. However, many adjacent inspect/verify/check pairs across CRL, OCSP, CMS, timestamp, and PKCS#12 create realistic routing ambiguity for less expert agents.

Naming Consistency5/5

Every tool is snake_case and starts with a verb, with resource nouns reused consistently (certificate, crl, ocsp, cms, timestamp, pkcs12). Minor variation such as open_pkcs12 vs inspect_pkcs12 still fits the overall pattern.

Tool Count2/5

34 tools is a very large surface; even for a broad PKI domain it exceeds the comfortable range and splits capabilities across many narrowly scoped calls. The scope justifies most tools individually, but the set is heavy for an agent to navigate reliably.

Completeness4/5

Coverage is unusually deep across certificates, CSRs, chain building/validation, CRL/OCSP, CMS, timestamping, keys, PKCS#12, ASN.1/PEM, and error introspection. Gaps are mostly intentional doctrine — no key generation/export, no network fetching — plus no enveloped CMS encryption/decryption, so the lifecycle is not fully closed.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides a unified tool for TLS certificate analysis with automatic OpenSSL integration, allowing users to fetch, analyze and assess security of TLS certificates without manual PEM handling.
    2
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    MCP server for offline verification of signed artifacts — receipts, manifests, and audit bundles. MIT licensed, works without accounts or API calls. Tools: self_test, verify_receipt, verify_bundle, explain_artifact.
    4
    45 npm
    6
    Apache 2.0
  • A
    license
    B
    quality
    D
    maintenance
    Defense-grade cryptographic compliance and analysis tools for MCP, including FIPS 140-3 validation, CNSA 2.0 analysis, post-quantum readiness assessment, and classical cipher utilities.
    18
    2
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    A local MCP server for AI-assisted PKI and ASN.1 workflows, providing tools for certificate parsing, key management, and PKCS#12 processing.
    28
    27 npm
    MIT