pkinative-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@pkinative-mcpcheck if this PEM certificate is expired and who issued it"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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.
✨ 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 |
| read |
|
|
| read | Object identifiers in one call: |
|
| read |
|
|
| read | The X.690 node tree of any DER (BER with |
|
| artefact | A JSON node |
|
Certificates and requests
Tool | Kind | What it does | Key refusal codes |
| read | The RFC 5280 report of one certificate: serial, algorithms, issuer / subject (RFC 4514 + RDNs), validity, public key, the 19 extension kinds decoded ( |
|
| artefact | Issue an X.509 v3 certificate from |
|
| 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 |
|
| read | One |
|
| verdict | The RFC 6125 identity verdict: |
|
| verdict | May |
|
| 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 ( |
|
| artefact | A PKCS#10 request from |
|
Paths, CRLs and OCSP
Tool | Kind | What it does | Key refusal codes |
| verdict | The one-call trust verdict: a path from |
|
| verdict | Path discovery only: which intermediates lead from |
|
| verdict | RFC 5280 §6.1 validation of a path you ordered (leaf first) against your anchors at |
|
| read | The RFC 5280 §5 report of one CRL: issuer, updates, CRL number, delta indicator, IDP, entry count (walked, never all decoded), extensions; |
|
| verdict | One |
|
| artefact | The DER OCSPRequest (RFC 6960) for |
|
| read | The RFC 6960 report of one response: status, responder id, producedAt, every SingleResponse (CertID, status, updates), extensions, embedded certificates, nonce presence; |
|
| verdict | One |
|
CMS and time-stamps
Tool | Kind | What it does | Key refusal codes |
| artefact | An RFC 5652 SignedData over |
|
| verdict | The one-call verdict on a SignedData: for every signer the signed attributes, the content digest (attached, or |
|
| 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); |
|
| artefact | The one edit a signed message legitimately receives afterwards: |
|
| artefact | The RFC 3161 TimeStampReq you POST to a TSA yourself: imprint over |
|
| read | A TimeStampResp, a TimeStampToken or a bare TSTInfo (auto-detected, or |
|
| verdict |
|
|
Private keys and PKCS#12
Tool | Kind | What it does | Key refusal codes |
| read | Describe a PKCS#8 key without its password (with |
|
| read | Describe a |
|
| 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; |
|
Meta
Tool | Kind | What it does | Key refusal codes |
| 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 |
|
| 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); |
|
| 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. |
|
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_requestandcreate_timestamp_requestproduce bytes you transport. Every tool advertisesopenWorldHint: 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_asn1andencode_asn1refuse 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'sPKI_REASON_*codes (43 of them);isErroris reserved for an input the engine refuses (one of the 57PKI_*codes, passed through verbatim withdata.errorClassand 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 explicitrsaScheme('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 (97PKI_DIAG_*codes) are not errors unlessstrict: true; the engine's 22 limits raise per call and never above the operator ceilings.🪙 Token-frugal — read and verdict tools accept
verbosity: 'summary'andfields: [...]; artefacts arrive once as an embeddedresourceblock or aresource_link, never duplicated intostructuredContent.🔁 Reproducible on request — pin
at,notBefore,signingTime,serialNumberandnonce(or let the operator pin the instant withPKINATIVE_MCP_AT) and an artefact or a verdict is a pure function of its inputs;structuredContent.sha256is 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 embeddedresourcecontent block (PEM astext, DER asblob, with its MIME type:application/pkix-cert,application/pkcs10,application/pkcs7-signature,application/pkix-crl,application/ocsp-request,application/timestamp-query, …);structuredContentcarries{ mode, encoding, sizeBytes, sha256, summary, diagnostics }— withencoding: 'hex'the bytes land instructuredContent.hexinstead (inline only, ≤ 64 KiB).file— the artefact is written inside the sandbox configured byPKINATIVE_MCP_OUTPUT_DIR(exclusive create, never overwritten →OUTPUT_EXISTS; ≤ 1 GiB) and the result carries aresource_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 everypathinput, so acreate_csr → create_certificate → verify_certificate_chainchain 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 afterverbosity. Every path that matched nothing — nested paths included — is omitted and reported in_meta.unmatchedFields(with_meta.availableFields); a path holding a__proto__,constructororprototypesegment isVALIDATION_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, 43PKI_REASON_*verdict reasons and 97PKI_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-mcpRequirements: 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/discoverreplaces the session handshake; every result carriesresultTypeand the_metaserverInfoenvelope. Over HTTP, 2026-07-28 clients sendMcp-Method/Mcp-Nameheaders with eachPOST /mcp.Cache hints —
tools/listandprompts/listarepublicwith a 24 httlMs,server/discoverispublicfor 1 h, andresources/list/resources/templates/list/resources/readareprivatewithttlMs: 0(sandbox files are per-host user data).Protocol errors —
tools/callwith an unknown tool name is a JSON-RPC error (-32602,[UNKNOWN_TOOL] Unknown tool: …) rather than anisErrorresult, and an unknown resource URI is-32602(Invalid params), as the specification requires;isError: trueis reserved for execution failures.Automatic legacy fallback — a client that opens with
initializeis 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 /mcponly;GET/DELETE /mcpanswer 405 (no SSE resumability; the server is stateless). Bound to127.0.0.1with aHost/Originguard (foreign values → 403);PKINATIVE_MCP_HTTP_TOKENadds an opt-in bearer-token gate (401+WWW-Authenticatewithout 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 beforeinitializeis dropped without a reply on the legacy path.Output schemas — every
structuredContentvalidates against the tool'soutputSchema(a 2026-07-28 MUST), includingverbosity: 'summary'andfieldsprojections: the read and verdict tools declare projectable schemas (all properties optional,additionalProperties: falsekept). Input schemas carry no$schemakeyword and no$refby policy (some hosts forwardinputSchemato function-calling APIs that reject unknown keywords).serverInfocarrieswebsiteUrl; the resource template ispkinative://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 |
ChatGPT and other Streamable HTTP hosts | HTTP | legacy stateless streamable HTTP — unchanged |
MCP 2026-07-28 clients (SDK v2 | stdio / HTTP |
|
Compatibility matrix — every negotiated revision, on both transports, with the test that proves it:
Revision | Era | stdio | HTTP | Proven by |
2024-10-07 | legacy | ✓ | ✓ |
|
2024-11-05 | legacy | ✓ | ✓ |
|
2025-03-26 | legacy | ✓ | ✓ |
|
2025-06-18 | legacy | ✓ | ✓ |
|
2025-11-25 | legacy | ✓ | ✓ |
|
2026-07-28 | modern ( | ✓ | ✓ (+ bearer token) |
|
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 |
| (unset) | Absolute path of the one sandbox directory: every |
| (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 |
| (unset → stdio) | When set to a valid port (1–65535), serves Streamable HTTP on |
| (unset) | (secret) Opt-in bearer token for the HTTP transport (≥ 16 characters, no whitespace — a weaker value aborts startup). When set, every |
| (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 ( |
| (unset → wall clock) | Pinned instant (ISO 8601, or epoch milliseconds) every omitted |
|
| Operator ceiling for |
|
| Operator ceiling for |
|
| Operator ceiling for |
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 |
|
| Exactly one. |
|
|
|
|
| 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 |
| string |
|
|
|
|
|
| Raise the engine's named bounds for this call, trusted input only; absent keys keep the engine defaults; every value is an integer ≥ 1; |
|
| Exactly one of |
|
| A verdict is relative to the anchors you name (at least one). |
|
|
|
|
|
|
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
passwordinput or the operator-sidePKINATIVE_MCP_PASSWORD— one source per call (both at once isVALIDATION_ERROR);SECRET_REQUIREDwhen 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 (everypathinput,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 isSECURITY_VIOLATIONon 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 byresources/*— andresources/list/resources/readrefuse 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 isSECURITY_VIOLATION. The same content test guards the generic encoders (convert_pem encodetakes exactly one DER value and refuses key material) and the content inputs (content.pathofsign_cms,data.pathofcreate_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.tsand advertised bydescribe_engine.caps:
Cap | Value |
stdio frame / HTTP body | 256 MiB |
decoded inline PKI inputs per call / inline | 128 MiB / 128 MiB |
| 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 |
| 200 default / 2000 max |
SHAKE256 output / password / | 1 MiB / 4096 / 16 / 32 bytes / 20 000 |
diagnostics per result | 200 (de-duplicated by code and path) |
| 1000 / 8 / 50 MiB |
response cache TTL / size | 3600 s / 256 MiB |
engine defaults ( | the 22 |
The cache gate (
isCacheable,src/server.ts) — withPKINATIVE_MCP_CACHE_DIRset, a result is served from the cache only when nothing about it depends on the environment, a secret, the clock or randomness: never apathinput (a path is a reference, not content), neveroutputMode: 'file', never a call carrying apasswordor asigner, never a'random'serial or nonce, never a'now'or unpinned instant, neverdescribe_engineordraft_governance_issue, and never any password-accepting tool whilePKINATIVE_MCP_PASSWORDis 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 oropenssl 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 unlessPKINATIVE_MCP_HTTP_TOKENis set (then401without 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_chainwithserverNameandpurposesfor 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 withnode: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,
openssl3.x onPATH(OPENSSL_BINoverrides; LibreSSL and OpenSSL < 3.0 are refused with the reason): 12 command families —x509 -textover every certificate (DER and PEM),verifyto each corpus CA (-purpose sslserverfor the leaves,-untrustedfor the intermediate,-attimethe pinned instant),req -verify,crl -text,cms -verify(attached and detached,-CAfilethe fixture root),asn1parse,ocsp -req_text,ts -query -text— with the raw transcripts undertest-output/pki/reports/. Skipped with the reason when OpenSSL is absent; a failure underPKINATIVE_INTEROP_REQUIRED=1or 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 failsnpm 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 |
|
|
|
|
| vitest (dot reporter) / with v8 coverage — thresholds in |
|
|
| runs the 89 |
| the 98-sample baseline: the built server under the hermetic env → |
| the OpenSSL corpus (23 + 5) written to |
| the vitest form of level 1 ( |
| the built |
| every count, version, tool, code, operator variable, link and anchor in the docs held to |
| regenerates |
| the mechanical version bump (never commits, tags or publishes) |
| 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/listRun 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 seedocs/guides/LOCAL_TESTING.mdfor 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.mdCHANGELOG.mdmirrors each release bullet listGitHub Release body is copied from
release-notes/vX.Y.Z.mdnpx tsx scripts/release-prepare.ts --version X.Y.Zapplies the mechanical part of a version bump (it never commits, tags or publishes); the version moves in lock-step acrosspackage.json,src/version.ts,server.jsonandCITATION.cff(tests/metadata.test.ts) and indocs/assets/ecosystem.json(npm run verify:docs, rulemanifest-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 protectednpm-publishenvironment: 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 ReleaseEvery 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 withpersist-credentials: false, installs withnpm ci --ignore-scriptsand runs under least-privilegepermissions; actions are pinned by commit SHA; the OpenSSL interoperability gate is blockingHuman-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 toolsamend_cmsAmend CMS (add an unsigned attribute or an RFC 3161 counter time-stamp)AIdempotent
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).
| Name | Required | Description | Default |
|---|---|---|---|
| token | No | The 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. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| encoding | No | Artefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'der'. | der |
| allowSha1 | No | Accept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write. | |
| attribute | No | One 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). | |
| operation | Yes | 'add-unsigned-attribute' writes `attribute` verbatim; 'add-timestamp' writes `token` as id-aa-signatureTimeStampToken after checking its imprint. | |
| outputMode | No | '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 |
| outputPath | No | Relative 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. | |
| signedData | Yes | The CMS SignedData to amend (CMS / PKCS7 PEM, or DER: .p7s .p7m): exactly one of pem / derBase64 / path. | |
| signerIndex | No | Which signer receives the attribute (encoded order; NOT_FOUND past the last). | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
Output Schema
| Name | Required | Description |
|---|---|---|
| hex | No | The artefact as lowercase hex (when encoding='hex'). |
| mode | Yes | |
| sha256 | Yes | SHA-256 of the DER bytes (lowercase hex) — the reproducibility proof. |
| summary | Yes | |
| encoding | Yes | |
| filePath | No | Sandboxed absolute path (when outputMode='file'). |
| sizeBytes | Yes | Size of the DER artefact. |
| diagnostics | Yes | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| diagnosticCounts | Yes | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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)ARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| at | No | The 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). | |
| leaf | Yes | The end-entity certificate to start from: exactly one of pem / derBase64 / path. | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| policy | No | RFC 5280 §6.1.1 policy inputs. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| purposes | No | Extended key usages the leaf must allow: serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, ocspSigning, any, or a dotted OID. | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| candidates | No | Untrusted 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. | |
| trustAnchors | Yes | Trust 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. | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
Output Schema
| Name | Required | Description |
|---|---|---|
| path | No | The path found, leaf first, up to (and including) the trust anchor; the leaf alone (with the reason) when no anchor was reached. |
| found | No | A 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. |
| valid | No | The engine verdict — always false here, because no signature was verified (PKI_REASON_SIGNATURE_NOT_CHECKED on every link). Read `found`. |
| reasons | No | Why 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. |
| explored | No | Candidate paths explored (bounded by limits.maxPathsExplored). |
| pathLength | No | |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| evaluatedAt | No | The ISO instant used — re-pass it as `at` to reproduce the result. |
| reasonCodes | No | |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| evaluatedAtSource | No | |
| signaturesChecked | No | Always false: run validate_certificate_path or verify_certificate_chain for the signatures. |
| structuralReasons | No | reasons[] without the PKI_REASON_SIGNATURE_NOT_CHECKED markers: what actually stopped the walk (empty when found). |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
| structuralReasonCodes | No |
TDQS
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.
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.
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.
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.
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.
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)ARead-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'.
| Name | Required | Description | Default |
|---|---|---|---|
| ip | No | The reference IPv4 or IPv6 address. Exclusive with host. | |
| host | No | The reference DNS name as it appeared in the URL (one trailing dot is ignored). Exclusive with ip. | |
| chain | No | The 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). | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| presented | No | Match form: the certificate's dNSName, possibly a wildcard. With reference; exclusive with certificate / host / ip / chain. | |
| reference | No | Match form: the reference host name. | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| certificate | No | The certificate to judge (certificate form): exactly one of pem / derBase64 / path. With exactly one of host / ip. Exclusive with presented / reference. | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| allowWildcards | No | Accept a wildcard name (*.example.test) at all. Default true; false is right for an internal PKI that issues none. | |
| allowCommonNameFallback | No | Fall 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
| Name | Required | Description |
|---|---|---|
| form | No | |
| match | No | Match form: whether presented matches reference. |
| valid | No | The verdict (the match outcome in the match form). false is a negative verdict, never an error. |
| reasons | No | Why 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. |
| subject | No | Certificate form: RFC 4514 subject of the certificate. |
| presented | No | Match form: the presented name. |
| reference | No | The reference identity judged. |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| reasonCodes | No | |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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)ARead-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'.
| Name | Required | Description | Default |
|---|---|---|---|
| chain | No | The 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. | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| purpose | Yes | The purpose needed: serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, ocspSigning, any, or a dotted KeyPurposeId OID. | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| certificate | Yes | The end-entity certificate to judge: exactly one of pem / derBase64 / path. | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| restrictIssuers | No | Apply 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. | |
| requireExplicitPurpose | No | Require 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
| Name | Required | Description |
|---|---|---|
| valid | No | The verdict. false is a negative verdict (reasons[]), never an error. |
| purpose | No | The purpose as given. |
| reasons | No | Why 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. |
| subject | No | RFC 4514 subject of the certificate. |
| pathLength | No | 1 + the chain certificates (reasons name path[i]). |
| purposeOid | No | The KeyPurposeId OID judged. |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| purposeName | No | The registry name of that OID. |
| reasonCodes | No | |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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)ARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| at | No | The 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). | |
| crl | Yes | The base CRL (X509 CRL PEM or DER): exactly one of pem / derBase64 / path. | |
| delta | No | A 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. | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| issuer | No | The 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. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| allowSha1 | No | Accept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write. | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| certificate | Yes | The certificate to check: exactly one of pem / derBase64 / path. | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| staleTolerance | No | Milliseconds past nextUpdate the evidence is still accepted (default: the engine policy, none). |
Output Schema
| Name | Required | Description |
|---|---|---|
| valid | No | true = the certificate is not revoked according to fresh, in-scope, signature-verified evidence. false is a negative verdict (reasons[]), never an error. |
| isDelta | No | Whether the BASE list itself carries a deltaCRLIndicator (false for a proper base CRL). |
| reasons | No | Why 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. |
| crlNumber | No | The base CRL number (decimal), null when absent. |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| evaluatedAt | No | The ISO instant used — re-pass it as `at` to reproduce the verdict. |
| reasonCodes | No | |
| deltaCrlNumber | No | The delta CRL number (present only with `delta`). |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| evaluatedAtSource | No | |
| signatureVerified | No | The base CRL signature against `issuer`; null when no issuer was given (then the engine reports the list as unverified). |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
| deltaSignatureVerified | No | The delta CRL signature against `issuer` (present only with `delta`). |
TDQS
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.
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.
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.
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.
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.
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)ARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| at | No | The 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). | |
| nonce | No | The 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). | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| issuer | Yes | The 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). | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| response | Yes | The OCSP response (DER, as the responder returned it): exactly one of pem / derBase64 / path. | |
| allowSha1 | No | Accept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write. | |
| responder | No | The 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. | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| certificate | Yes | The certificate whose status is asked: exactly one of pem / derBase64 / path. | |
| requireNonce | No | Refuse a response that carries no nonce (RFC 8954). | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| hashAlgorithm | No | The CertID hash to recompute with. Default: the one the response used for this serial, else SHA-1. | |
| staleTolerance | No | Milliseconds past nextUpdate the evidence is still accepted (default: the engine policy, none). | |
| futureTolerance | No | Milliseconds a thisUpdate / producedAt may lie after `at` (clock skew) and still be accepted (default: the engine policy). | |
| responderTrusted | No | State 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
| Name | Required | Description |
|---|---|---|
| valid | No | true = a fresh, correctly signed, authorised answer says good. false is a negative verdict (reasons[]: revoked, unknown, stale, unauthorised, unverified, nonce …), never an error. |
| status | No | The status the response gives for this serial; null when the response holds no answer for it. |
| reasons | No | Why 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. |
| certIdHex | No | The recomputed DER CertID (hex). |
| responder | No | The subject of the certificate that verified the signature. |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| evaluatedAt | No | The ISO instant used — re-pass it as `at` to reproduce the verdict. |
| reasonCodes | No | |
| hashAlgorithm | No | The hash the CertID was recomputed with. |
| diagnosticCount | No | |
| responderSource | No | Where the signer came from: the `responder` input, the certificates embedded in the response, or the issuer itself. |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| evaluatedAtSource | No | |
| signatureVerified | No | A responder candidate verified the response signature. |
| responderAuthorized | No | The signer is the CA itself, a delegate the CA issued with id-kp-OCSPSigning, or responderTrusted was set. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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)ARead-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:[…].
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | '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 |
| input | Yes | The 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. | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| algorithm | No | fingerprint: SHA-1 | SHA-256 (default) | SHA-384 | SHA-512. key-id: SHA-1 (default, RFC 5280 method 1) | SHA-256. Not for shake256. | |
| separator | No | Placed between octets of `formatted` (default ':'; '' for none). | : |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| webcrypto | No | fingerprint only: digest through Web Crypto instead of the pure-TypeScript hashes (identical result). | |
| letterCase | No | Hex case of `formatted` (`hex` is always lowercase). | upper |
| outputLength | No | shake256 only: output bytes (default 32, ≤ 1048576; 64 for an Ed448 CMS signer). | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
Output Schema
| Name | Required | Description |
|---|---|---|
| hex | No | The digest as lowercase hex, no separator. |
| from | No | key-id: where the public key came from. |
| mode | No | |
| label | No | The PEM label of the input, null for DER. |
| algorithm | No | SHA-1 | SHA-256 | SHA-384 | SHA-512, or 'SHAKE256'. |
| formatted | No | The digest with the separator and case asked for (AB:CD:… by default). |
| sizeBytes | No | Bytes digested: the DER of the input (fingerprint, shake256) or the public key bits (key-id). |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| outputLength | No | shake256: the output length in bytes. |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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)AIdempotent
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:[…].
| Name | Required | Description | Default |
|---|---|---|---|
| mode | Yes | 'decode' lists the blocks of a PEM text (and extracts one with extractIndex); 'encode' wraps DER bytes into one PEM block. | |
| input | Yes | The 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. | |
| label | No | decode: 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). | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| encoding | No | Artefact 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). | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| outputMode | No | '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 |
| outputPath | No | Relative 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. | |
| extractIndex | No | decode: 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). | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
Output Schema
| Name | Required | Description |
|---|---|---|
| mode | No | |
| count | No | decode: number of blocks listed. |
| label | No | encode: the label written. |
| blocks | No | decode: every block of the text (or those with `label`), in order. A private-key block is listed by label and size only. |
| labels | No | decode: the label of every block listed. |
| sha256 | No | encode: SHA-256 of the DER wrapped (lowercase hex). |
| artefact | No | The artefact delivered alongside the report: its facts here, its bytes once as the embedded resource (or the resource_link in file mode). |
| extracted | No | decode: the block delivered as the artefact. |
| sizeBytes | No | encode: the DER size wrapped. |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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).
| Name | Required | Description | Default |
|---|---|---|---|
| spec | Yes | What to write into the certificate. Unknown members are refused. | |
| limits | No | Raise 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. | |
| signer | Yes | 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| encoding | No | Artefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'pem'. | pem |
| allowSha1 | No | Accept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write. | |
| outputMode | No | '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 |
| outputPath | No | Relative 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. | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| subjectPublicKey | No | The 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). | |
| issuerCertificate | No | The 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
| Name | Required | Description |
|---|---|---|
| hex | No | The artefact as lowercase hex (when encoding='hex'). |
| mode | Yes | |
| sha256 | Yes | SHA-256 of the DER bytes (lowercase hex) — the reproducibility proof. |
| summary | Yes | |
| encoding | Yes | |
| filePath | No | Sandboxed absolute path (when outputMode='file'). |
| sizeBytes | Yes | Size of the DER artefact. |
| diagnostics | Yes | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| diagnosticCounts | Yes | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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).
| Name | Required | Description | Default |
|---|---|---|---|
| spec | Yes | What to write into the request: the subject and the extensions to ask for. Unknown members are refused. | |
| limits | No | Raise 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. | |
| signer | Yes | 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| encoding | No | Artefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'pem'. | pem |
| allowSha1 | No | Accept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write. | |
| outputMode | No | '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 |
| outputPath | No | Relative 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. | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| subjectPublicKey | No | The 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
| Name | Required | Description |
|---|---|---|
| hex | No | The artefact as lowercase hex (when encoding='hex'). |
| mode | Yes | |
| sha256 | Yes | SHA-256 of the DER bytes (lowercase hex) — the reproducibility proof. |
| summary | Yes | |
| encoding | Yes | |
| filePath | No | Sandboxed absolute path (when outputMode='file'). |
| sizeBytes | Yes | Size of the DER artefact. |
| diagnostics | Yes | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| diagnosticCounts | Yes | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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).
| Name | Required | Description | Default |
|---|---|---|---|
| label | No | The PEM label for encoding:'pem' (default OCSP REQUEST; upper-case words and digits). A private-key label is refused. | |
| nonce | No | The 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). | |
| issuer | Yes | The CA certificate that issued it (its name and key are hashed into the CertID): exactly one of pem / derBase64 / path. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| encoding | No | Artefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'der'. | der |
| certIdOnly | No | Return the bare DER CertID (RFC 6960 §4.1.1) instead of the request (der or hex only — a CertID has no PEM label). | |
| outputMode | No | '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 |
| outputPath | No | Relative 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. | |
| certificate | Yes | The certificate whose status to ask for: exactly one of pem / derBase64 / path. | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| hashAlgorithm | No | The 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
| Name | Required | Description |
|---|---|---|
| hex | No | The artefact as lowercase hex (when encoding='hex'). |
| mode | Yes | |
| sha256 | Yes | SHA-256 of the DER bytes (lowercase hex) — the reproducibility proof. |
| summary | Yes | |
| encoding | Yes | |
| filePath | No | Sandboxed absolute path (when outputMode='file'). |
| sizeBytes | Yes | Size of the DER artefact. |
| diagnostics | Yes | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| diagnosticCounts | Yes | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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).
| Name | Required | Description | Default |
|---|---|---|---|
| data | No | The 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. | |
| label | No | The PEM label for encoding:'pem' (default TIMESTAMP REQUEST; upper-case words and digits). A private-key label is refused. | |
| nonce | No | A 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. | |
| limits | No | Raise 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. | |
| policy | No | The TSA policy to issue under (dotted OID); the TSA's default when absent. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| certReq | No | Ask the TSA to include its certificate in the token (what verify_timestamp needs unless you pass candidates). | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| encoding | No | Artefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'der'. | der |
| digestHex | No | The digest to stamp, already computed with hashAlgorithm (its length must match). Exclusive with data. Hexadecimal (an 0x prefix, colons and spaces are tolerated). | |
| outputMode | No | '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 |
| outputPath | No | Relative 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. | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| hashAlgorithm | No | The message imprint algorithm. | SHA-256 |
Output Schema
| Name | Required | Description |
|---|---|---|
| hex | No | The artefact as lowercase hex (when encoding='hex'). |
| mode | Yes | |
| sha256 | Yes | SHA-256 of the DER bytes (lowercase hex) — the reproducibility proof. |
| summary | Yes | |
| encoding | Yes | |
| filePath | No | Sandboxed absolute path (when outputMode='file'). |
| sizeBytes | Yes | Size of the DER artefact. |
| diagnostics | Yes | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| diagnosticCounts | Yes | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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)ARead-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'].
| Name | Required | Description | Default |
|---|---|---|---|
| read | No | Apply this typed reader to the selected node (the root without nodePath) and return `value`; a wrong tag is PKI_ASN1_UNEXPECTED_TAG. | |
| input | Yes | The 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). | |
| label | No | reencode + 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. | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| encoding | No | Artefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'der'. | der |
| nodePath | No | Child 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. | |
| reencode | No | Re-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. | |
| sequence | No | The input is several top-level values back to back: decode each (exclusive with nodePath / read / reencode). | |
| timeType | No | read:'time' on an IMPLICIT-tagged node: the time type (required then). | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| outputMode | No | '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 |
| outputPath | No | Relative 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. | |
| stringType | No | read:'string' on an IMPLICIT-tagged node: the charset to decode with (required then — PKI_API_MISUSE otherwise). | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| maxPreviewBytes | No | Content bytes of a primitive shown in `preview` (hex; 0 hides previews; default 32). | |
| allowTrailingData | No | Accept bytes after the outermost value instead of PKI_ASN1_TRAILING_DATA (single value only; sequence:true decodes them instead). |
Output Schema
| Name | Required | Description |
|---|---|---|
| label | No | The PEM label of the input, null for DER. |
| nodes | No | The selected subtree (or every top-level tree with sequence:true), depth-first in input order. |
| value | No | read: 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 }. |
| rootTag | No | tagName of the first node listed (null when nothing was listed). |
| artefact | No | The artefact delivered alongside the report: its facts here, its bytes once as the embedded resource (or the resource_link in file mode). |
| reencode | No | |
| identical | No | reencode: the bytes are identical. |
| nodeCount | No | |
| sizeBytes | No | Bytes of the input object. |
| truncated | No | true when the listing stopped at 20000 nodes (use nodePath to walk the rest). |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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)ARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| oid | Yes | The extension OID (2.5.29.19 basicConstraints, 2.5.29.17 subjectAltName, …) — it selects the decoder. | |
| value | No | The extnValue content as DER bytes (derBase64, or a sandbox .der / .bin / .asn1 file): exactly one of pem / derBase64 / path. Exclusive with valueHex. | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| critical | No | The criticality to report and to check against the kind (a kind RFC 5280 requires critical or non-critical gets a diagnostic). | |
| valueHex | No | The extnValue content (the DER inside the OCTET STRING) as hex. Exclusive with value. Hexadecimal (an 0x prefix, colons and spaces are tolerated). | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
Output Schema
| Name | Required | Description |
|---|---|---|
| oid | No | |
| kind | No | One of the 19 decoded kinds, or unknown. |
| name | No | The registry name of the OID. |
| decoded | No | The decoded members of the kind (cA / pathLenConstraint, usages, purposes, names, keyIdentifier, …); empty for an unknown OID. |
| critical | No | |
| sizeBytes | No | Size of the value DER. |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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 capabilitiesARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
Output Schema
| Name | Required | Description |
|---|---|---|
| caps | No | |
| engine | No | |
| server | No | |
| network | No | |
| sandbox | No | |
| ceilings | No | |
| refusals | No | |
| unexposed | No | |
| randomness | No | |
| registries | No | |
| capabilities | No | |
| defaultLimits | No |
TDQS
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.
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.
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.
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.
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.
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)AIdempotent
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.
| Name | Required | Description | Default |
|---|---|---|---|
| title | Yes | Concise issue title (imperative, no trailing period). | |
| summary | Yes | One or two paragraphs describing the problem or proposal. Never a private key or a password. | |
| issueType | Yes | Issue category: 'bug' | 'feature' | 'security' | 'docs' | 'performance'. | |
| outputMode | No | '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 |
| outputPath | No | Relative .md path inside the sandbox (only when outputMode='file'). | |
| targetRepo | No | Destination 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 |
| reproduction | Yes | The minimal, locally-executed reproduction that justifies this issue (test material only). | |
| actualBehavior | No | What actually happened (defaults to the reproduction result when omitted). | |
| affectedPackages | No | Packages impacted by this issue (e.g. ["pkinative-mcp"], ["pkinative"]). | |
| expectedBehavior | Yes | What you expected to happen instead. | |
| duplicateSearchPerformed | Yes | MUST be true: confirms you searched open AND closed issues/PRs for duplicates before drafting. |
Output Schema
| Name | Required | Description |
|---|---|---|
| title | Yes | |
| filePath | No | Sandboxed absolute path (when outputMode='file'). |
| warnings | Yes | |
| issueType | Yes | |
| sizeBytes | Yes | |
| compliance | Yes | |
| outputMode | Yes | |
| targetRepo | Yes | |
| draftMarkdown | Yes | The full draft, ready for a human to review and submit. |
TDQS
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.
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.
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.
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.
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.
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)AIdempotent
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).
| Name | Required | Description | Default |
|---|---|---|---|
| spec | Yes | An 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. | |
| label | No | The PEM label for encoding:'pem' (required then; upper-case words and digits). A private-key label is refused. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| encoding | No | Artefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'der'. | der |
| outputMode | No | '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 |
| outputPath | No | Relative 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. | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
Output Schema
| Name | Required | Description |
|---|---|---|
| hex | No | The artefact as lowercase hex (when encoding='hex'). |
| mode | Yes | |
| sha256 | Yes | SHA-256 of the DER bytes (lowercase hex) — the reproducibility proof. |
| summary | Yes | |
| encoding | Yes | |
| filePath | No | Sandboxed absolute path (when outputMode='file'). |
| sizeBytes | Yes | Size of the DER artefact. |
| diagnostics | Yes | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| diagnosticCounts | Yes | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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)AIdempotent
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.
| Name | Required | Description | Default |
|---|---|---|---|
| spec | No | The 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). | |
| label | No | The RFC 7468 label for encoding:'pem' (required then, refused otherwise); a key-bearing label is refused. | |
| limits | No | Raise 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. | |
| signer | No | signature-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). | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| encoding | No | Artefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'hex'. | hex |
| allowSha1 | No | Accept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write. | |
| structure | Yes | The X.509 building block to encode. | |
| outputMode | No | '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 |
| outputPath | No | Relative 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. | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
Output Schema
| Name | Required | Description |
|---|---|---|
| hex | No | The artefact as lowercase hex (when encoding='hex'). |
| mode | Yes | |
| sha256 | Yes | SHA-256 of the DER bytes (lowercase hex) — the reproducibility proof. |
| summary | Yes | |
| encoding | Yes | |
| filePath | No | Sandboxed absolute path (when outputMode='file'). |
| sizeBytes | Yes | Size of the DER artefact. |
| diagnostics | Yes | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| diagnosticCounts | Yes | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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)ARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| code | No | The code to explain (PKI_*, PKI_REASON_*, PKI_DIAG_* or a wrapper code such as SECURITY_VIOLATION). Omit with list:true to enumerate. | |
| kind | No | Restrict a listing to one vocabulary. | |
| list | No | List the codes (optionally of one kind) instead of explaining one. | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
Output Schema
| Name | Required | Description |
|---|---|---|
| cwe | No | |
| code | No | |
| kind | No | |
| codes | No | |
| count | No | |
| since | No | |
| remedy | No | |
| summary | No | When the code is raised / returned. |
| severity | No | |
| standard | No | |
| errorClass | No | The coarse class (usage, input, parse, security, limit, unsupported, crypto, password, check, internal). |
| engineClass | No | The PkiError subclass. |
TDQS
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.
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.
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.
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.
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.
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)ARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| issuer | No | The 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. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| allowSha1 | No | Accept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write. | |
| extension | No | Return only this decoded extension (NOT_FOUND when absent). Exclusive with rawExtensions. | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| certificate | Yes | The certificate (CERTIFICATE PEM or DER): exactly one of pem / derBase64 / path. | |
| fingerprints | No | Digests of the DER to report. | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| rawExtensions | No | Keep every extension undecoded (kind raw, valueDer as hex) — the way past PKI_X509_EXTENSION_MALFORMED. | |
| checkSelfSignature | No | Check the certificate against its own key (a self-signed root). Exclusive with issuer. | |
| requireAlgorithmMatch | No | Require the outer signatureAlgorithm to equal the TBS signature algorithm (RFC 5280 §4.1.1.2). |
Output Schema
| Name | Required | Description |
|---|---|---|
| issuer | No | |
| subject | No | |
| version | No | |
| notAfter | No | |
| validity | No | |
| extension | No | The one extension asked for with `extension`. |
| notBefore | No | |
| publicKey | No | |
| signature | No | The signature check asked for (issuer or self). ONE signature against ONE key — not a trust verdict. |
| sizeBytes | No | |
| extensions | No | |
| selfIssued | No | issuer DER equals subject DER (self-issued; a self-SIGNATURE is a separate check). |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| fingerprints | No | Colon-separated uppercase hex digests of the DER, by algorithm. |
| issuerString | No | RFC 4514 issuer. |
| serialNumber | No | |
| subjectString | No | RFC 4514 subject. |
| extensionKinds | No | |
| signatureValid | No | |
| diagnosticCount | No | |
| serialNumberHex | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| signatureAlgorithm | No | |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
| subjectPublicKeyInfo | No |
TDQS
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.
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.
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.
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.
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.
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)ARead-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:[…].
| Name | Required | Description | Default |
|---|---|---|---|
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| content | No | The detached content, for a signer WITHOUT signed attributes (such a signer signs the content itself); ignored otherwise: exactly one of text / base64 / path. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| allowSha1 | No | Accept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write. | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| signedData | Yes | The CMS SignedData (CMS / PKCS7 PEM, or DER: .p7s .p7m .p7b): exactly one of pem / derBase64 / path. | |
| signerIndex | No | Which signer the signature check targets (encoded order; NOT_FOUND past the last). | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| allowTrailingData | No | Ignore bytes after the ContentInfo (the zero padding of a PDF /Contents). Default false: trailing bytes are PKI_ASN1_TRAILING_DATA. | |
| signerCertificate | No | The 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
| Name | Required | Description |
|---|---|---|
| signers | No | |
| version | No | |
| crlCount | No | |
| sizeBytes | No | |
| contentType | No | |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| signerCount | No | |
| signerInfos | No | |
| certificates | No | The embedded certificates (a claim by whoever assembled the message). |
| contentLength | No | Bytes of the encapsulated content; null when the signature is detached. |
| contentTypeOid | No | |
| diagnosticCount | No | |
| signerSignature | No | The signature check asked for with signerCertificate: ONE signer against ONE key. A key check, not the verdict (verify_cms). |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| digestAlgorithms | No | |
| ocspResponseCount | No | |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
| signerSignatureValid | No |
TDQS
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.
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.
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.
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.
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.
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)ARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| crl | Yes | The CRL (X509 CRL PEM or DER): exactly one of pem / derBase64 / path. | |
| find | No | Look one serial up in the list (the entries are walked, never all decoded): exactly one of serialHex / certificate. | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| issuer | No | The 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. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| allowSha1 | No | Accept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write. | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
Output Schema
| Name | Required | Description |
|---|---|---|
| entry | No | The lookup asked for with `find`: whether the serial is listed, and the entry when it is. |
| issuer | No | |
| isDelta | No | Carries a deltaCRLIndicator (RFC 5280 §5.2.4). |
| revoked | No | |
| version | No | |
| crlNumber | No | Decimal (RFC 5280 §5.2.3); null when absent. |
| signature | No | The signature check asked for with `issuer`. ONE signature against ONE key — not a trust verdict. |
| sizeBytes | No | |
| entryCount | No | Entries in revokedCertificates (counted by walking, bounded by limits.maxRevokedCertificates). |
| extensions | No | |
| nextUpdate | No | |
| thisUpdate | No | |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| issuerString | No | RFC 4514 issuer. |
| baseCrlNumber | No | The base CRL number a delta applies to (decimal); null otherwise. |
| nextUpdateIso | No | |
| thisUpdateIso | No | |
| signatureValid | No | |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| signatureAlgorithm | No | |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
| issuingDistributionPoint | No | RFC 5280 §5.2.5, decoded (fullName, onlyContainsUserCerts, onlyContainsCACerts, onlySomeReasons, indirectCRL, onlyContainsAttributeCerts); null when absent = the list covers everything its CA issued. |
TDQS
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.
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.
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.
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.
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.
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)ARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| verify | No | Verify the request's signature under the key it carries (proof of possession; one Web Crypto verification). false skips it. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| request | Yes | The PKCS#10 request (CERTIFICATE REQUEST PEM or DER): exactly one of pem / derBase64 / path. | |
| allowSha1 | No | Accept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write. | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| rawExtensions | No | Keep every requested extension undecoded (kind raw, valueDer as hex) — the way past PKI_X509_EXTENSION_MALFORMED. |
Output Schema
| Name | Required | Description |
|---|---|---|
| subject | No | |
| version | No | v1 (encoded 0), the only version RFC 2986 defines. |
| publicKey | No | |
| signature | No | The proof of possession (verify:true): the signature under the key the request carries. Not trust — a CSR is unsigned by any CA. |
| sizeBytes | No | |
| attributes | No | Every attribute in encoded order (extensionRequest, challengePassword, …). |
| extensions | No | The extensions requested through extensionRequest (RFC 2985 §5.4.2), decoded; null when the request asks for none. A CA is free to ignore them. |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| subjectString | No | RFC 4514 subject. |
| extensionKinds | No | |
| signatureValid | No | |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| challengePassword | No | Whether a challengePassword attribute is present (its text is attacker-controlled and never reported). |
| signatureAlgorithm | No | |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
| subjectPublicKeyInfo | No |
TDQS
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.
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.
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.
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.
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.
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)ARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| response | Yes | The OCSP response (DER, as the responder returned it): exactly one of pem / derBase64 / path. | |
| responder | No | The 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). | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
Output Schema
| Name | Required | Description |
|---|---|---|
| status | No | OCSPResponseStatus (RFC 6960 §4.2.1). Only successful carries a signed answer. |
| responses | No | |
| signature | No | The signature check: against `responder`, or the first embedded certificate whose key verifies. ONE signature against ONE key — authorisation is judged by check_ocsp_status. |
| sizeBytes | No | |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| noncePresent | No | A nonce extension is present on the response. |
| basicResponse | No | The BasicOCSPResponse; null when the status is not successful. |
| signatureValid | No | |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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)ARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| pkcs12 | Yes | The PKCS#12 file (.p12 / .pfx; DER, or BER with encodingRules:'ber' for a Windows-written PFX): exactly one of pem / derBase64 / path. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| password | No | The 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. | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
Output Schema
| Name | Required | Description |
|---|---|---|
| mac | No | The MacData, or null when the file carries none (its integrity cannot be verified). |
| opened | No | With password: every SafeContents opened (the encrypted ones decrypted), its bags as allow-list views. |
| macKind | No | |
| version | No | |
| bagCount | No | Bags listed: those of `opened` when a password was given, else those of the plain SafeContents. |
| bagKinds | No | |
| contents | No | The AuthenticatedSafe entries, in encoded order. |
| sizeBytes | No | |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| macVerified | No | With 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. |
| contentCount | No | |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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)ARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| key | Yes | The 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). | |
| hash | No | Signature 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. | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| keyType | No | The 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. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| password | No | The 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. | |
| allowSha1 | No | Accept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write. | |
| rsaScheme | No | REQUIRED 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. | |
| tryImport | No | Also 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. | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| saltLength | No | RSA-PSS salt length in bytes (rsaScheme:'pss' only; default: the hash length). With tryImport:true only. | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
Output Schema
| Name | Required | Description |
|---|---|---|
| kind | No | |
| curve | No | |
| format | No | |
| scheme | No | The password-based encryption scheme of an encrypted key. |
| version | No | |
| imported | No | The import proof (full), or its ok flag (summary). |
| algorithm | No | |
| encrypted | No | true for an ENCRYPTED PRIVATE KEY (format encrypted-pkcs8). |
| publicKey | No | |
| sizeBytes | No | Size of the DER structure (ciphertext included for an encrypted key). |
| attributes | No | |
| encryption | No | |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| algorithmName | No | The name of the key algorithm OID (ecPublicKey, rsaEncryption, Ed25519 …), plain keys only. |
| encryptedBytes | No | |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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)ARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| as | No | Read the input as this structure. Omitted: auto-detected in the order response, token, tstinfo (the result's `as` says which). | |
| input | Yes | The time-stamp object: a TimeStampResp (.tsr), a TimeStampToken (.tst) or a bare TSTInfo (DER, or any PEM block): exactly one of pem / derBase64 / path. | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
Output Schema
| Name | Required | Description |
|---|---|---|
| as | No | What the input was read as. |
| token | No | The token (of a granted response, or the input itself): what it asserts and its CMS envelope. |
| status | No | The PKIStatusInfo of a response. Only granted / grantedWithMods carry a token. |
| genTime | No | |
| tstInfo | No | The bare TSTInfo (as:'tstinfo'). |
| sizeBytes | No | |
| imprintHex | No | |
| statusCode | No | |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| serialNumber | No | |
| hashAlgorithm | No | |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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)AIdempotent
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.
| Name | Required | Description | Default |
|---|---|---|---|
| hash | No | The hash of the RSA signature algorithm (with rsaScheme only). Default 'SHA-256'. | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| pkcs12 | Yes | The PKCS#12 file (.p12 / .pfx; DER, or BER with encodingRules:'ber' for a Windows-written PFX): exactly one of pem / derBase64 / path. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| encoding | No | Artefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'pem'. | pem |
| password | No | The 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. | |
| rsaScheme | No | What 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. | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| outputMode | No | '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 |
| outputPath | No | Relative 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. | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| exportCertificates | No | Attach 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. | |
| allowUnverifiedIntegrity | No | Accept 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
| Name | Required | Description |
|---|---|---|
| keys | No | Every 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). |
| valid | No | Everything opened and the integrity verified (or explicitly waived). false is a negative verdict (reasons[]), never an error. |
| reasons | No | Why 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. |
| artefact | No | With 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. |
| crlCount | No | |
| keyCount | No | |
| integrity | No | '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. |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| reasonCodes | No | |
| certificates | No | Every certificate that parsed, in encoded order — the keys' own and any chain the file carries. |
| exportSkipped | No | exportCertificates was asked but the file did not open (valid:false): nothing was exported. |
| diagnosticCount | No | |
| certificateCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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)ARead-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:[…].
| Name | Required | Description | Default |
|---|---|---|---|
| hex | No | decode: the octets to decode (content octets, or the whole TLV with form:'tlv'). Hexadecimal (an 0x prefix, colons and spaces are tolerated). | |
| form | No | encode / decode: 'content' (the base-128 content octets only) or 'tlv' (tag + length + content, i.e. the whole DER value). | content |
| mode | Yes | 'name' (registered names), 'validate' (X.660 validity), 'list' (the registry, paged), 'encode' (dotted → hex), 'decode' (hex → dotted + name). | |
| oids | No | name / validate / encode: the dotted OIDs (≤ 1000). | |
| input | No | decode: 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'. | |
| limit | No | list: page size (≤ 2000). | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| filter | No | list: case-insensitive substring of the name or the dotted OID. | |
| limits | No | Raise 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. | |
| offset | No | list: first entry returned. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| relative | No | encode / 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). | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
Output Schema
| Name | Required | Description |
|---|---|---|
| oid | No | decode: the dotted OID. |
| mode | No | |
| name | No | decode: the registered name (null when unregistered or relative). |
| oids | No | name: { oid, name|null }; validate: { oid, valid }; encode: { oid, hex }. |
| count | No | Number of OIDs / entries in the result. |
| limit | No | |
| total | No | list: entries matching the filter. |
| offset | No | |
| entries | No | list: the page of registry entries. |
| hasMore | No | |
| allValid | No | validate: true when every OID is valid. |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| sid | No | How 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 |
| crls | No | CRLs to embed (DER or X509 CRL PEM) (every PEM block of every item is taken). | |
| chain | No | Further 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. | |
| limits | No | Raise 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. | |
| signer | Yes | 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| content | No | The content to sign (hashed with the signer's digest; embedded unless detached): exactly one of text / base64 / path. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| detached | No | Omit the content from the SignedData (the signature travels without what it signs; most S/MIME and every PDF signature). | |
| encoding | No | Artefact encoding: 'pem' (RFC 7468 text), 'der' (raw bytes), 'hex' (lowercase hex in structuredContent, inline only, ≤ 64 KiB). Default 'der'. | der |
| allowSha1 | No | Accept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write. | |
| outputMode | No | '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 |
| outputPath | No | Relative 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. | |
| certificate | No | The 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). | |
| contentType | No | The eContentType (dotted OID). Default id-data (1.2.840.113549.1.7.1); any other makes a version 3 SignedData. | |
| signingTime | No | The 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. | |
| contentDigest | No | The 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). | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| signedAttributes | No | Further 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). | |
| unsignedAttributes | No | Unsigned 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. | |
| algorithmProtection | No | Write CMSAlgorithmProtection (RFC 6211, RFC 8933 §4). | |
| signingCertificateV2 | No | Write the ESS SigningCertificateV2 attribute (RFC 5035), binding the signature to this certificate — CAdES, PAdES and RFC 3161 require it. |
Output Schema
| Name | Required | Description |
|---|---|---|
| hex | No | The artefact as lowercase hex (when encoding='hex'). |
| mode | Yes | |
| sha256 | Yes | SHA-256 of the DER bytes (lowercase hex) — the reproducibility proof. |
| summary | Yes | |
| encoding | Yes | |
| filePath | No | Sandboxed absolute path (when outputMode='file'). |
| sizeBytes | Yes | Size of the DER artefact. |
| diagnostics | Yes | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| diagnosticCounts | Yes | Occurrences per diagnostic code. |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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)ARead-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'.
| Name | Required | Description | Default |
|---|---|---|---|
| at | No | The 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). | |
| path | Yes | The 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. | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| policy | No | RFC 5280 §6.1.1 policy inputs. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| allowSha1 | No | Accept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write. | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| trustAnchors | Yes | Trust 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. | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| checkSignatures | No | Verify 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
| Name | Required | Description |
|---|---|---|
| path | No | The path as validated, leaf first, up to (and including) the trust anchor it reached. |
| valid | No | The engine verdict. With checkSignatures:false it is always false (every link is PKI_REASON_SIGNATURE_NOT_CHECKED): read failingReasons for the structural verdict. |
| reasons | No | Why 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. |
| pathLength | No | |
| signatures | No | One 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. |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| evaluatedAt | No | The ISO instant used — re-pass it as `at` to reproduce the verdict. |
| reasonCodes | No | |
| failingReasons | No | The 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). |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| evaluatedAtSource | No | |
| signatureVerdicts | No | |
| signaturesChecked | No | |
| failingReasonCodes | No | |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
TDQS
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.
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.
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.
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.
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.
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)ARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| at | No | The 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). | |
| crls | No | CRLs (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. | |
| leaf | Yes | The end-entity certificate to judge: exactly one of pem / derBase64 / path. | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| policy | No | RFC 5280 §6.1.1 policy inputs. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| purposes | No | Extended key usages the leaf must allow: serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, ocspSigning, any, or a dotted OID. | |
| allowSha1 | No | Accept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write. | |
| ocspNonce | No | The 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). | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| candidates | No | Untrusted 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. | |
| serverName | No | The identity the leaf must be valid for (RFC 6125): exactly one of host / ip. | |
| trustAnchors | Yes | Trust 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. | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| ocspResponses | No | OCSP 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. | |
| requireOcspNonce | No | Refuse an OCSP response that does not echo the nonce. | |
| requireRevocation | No | Require 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
| Name | Required | Description |
|---|---|---|
| path | No | The path found, leaf first, up to (and including) the trust anchor. |
| valid | No | The verdict. false is a negative verdict (reasons[]), never an error. |
| reasons | No | Why 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. |
| explored | No | Candidate paths explored (bounded by limits.maxPathsExplored). |
| pathLength | No | |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| evaluatedAt | No | The ISO instant the verdict was evaluated at — re-pass it as `at` to reproduce the verdict. |
| reasonCodes | No | |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| evaluatedAtSource | No | |
| revocationEvidence | No | |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
| signatureVerifications | No |
TDQS
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.
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.
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.
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.
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.
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)ARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| at | No | The 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). | |
| crls | No | CRLs (DER or X509 CRL PEM) for offline revocation, added to those the message carries. Optional; this server never fetches a CRL. | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| content | No | The detached content (required for a detached signature; refused for an attached one): exactly one of text / base64 / path. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| purposes | No | Extended key usages every signer's chain must permit (emailProtection for S/MIME): serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, ocspSigning, any, or a dotted OID. | |
| allowSha1 | No | Accept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write. | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| candidates | No | Untrusted 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. | |
| signedData | Yes | The CMS SignedData to judge (CMS / PKCS7 PEM, or DER: .p7s .p7m): exactly one of pem / derBase64 / path. | |
| atTimeStamp | No | Judge 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. | |
| trustAnchors | Yes | Trust 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. | |
| contentDigest | No | The 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). | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| ocspResponses | No | OCSP responses (DER), added to those the message carries. Optional; this server never queries a responder. | |
| allowTrailingData | No | Ignore bytes after the ContentInfo (the zero padding of a PDF /Contents). Default false: trailing bytes are PKI_ASN1_TRAILING_DATA. | |
| requireRevocation | No | Require 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. | |
| requireSigningCertificate | No | Require every signer to name its certificate in an ESS signing-certificate attribute (CAdES / PAdES). | |
| requireAlgorithmProtection | No | Require every signer to carry CMSAlgorithmProtection (RFC 6211). |
Output Schema
| Name | Required | Description |
|---|---|---|
| valid | No | At least one signer and EVERY signer valid. false is a negative verdict (reasons[]), never an error. |
| reasons | No | Why 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. |
| signers | No | One verdict per signer, in encoded order. |
| atTimeStamp | No | |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| evaluatedAt | No | The ISO instant the chains were judged at — re-pass it as `at` to reproduce the verdict. |
| reasonCodes | No | |
| signerCount | No | |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| evaluatedAtSource | No | |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
| signatureVerifications | No | Signature verifications performed, chains and time-stamps included. |
TDQS
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.
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.
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.
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.
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.
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)ARead-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'.
| Name | Required | Description | Default |
|---|---|---|---|
| at | No | The 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). | |
| crls | No | CRLs (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. | |
| data | No | The data that was stamped (hashed with the token's own imprint algorithm): exactly one of text / base64 / path. | |
| token | No | The TimeStampToken (a ContentInfo over a TSTInfo; .tst): exactly one of pem / derBase64 / path. Exclusive with response. | |
| fields | No | Dot-paths to keep after verbosity (arrays map over elements); unmatched paths land in _meta.unmatchedFields. | |
| limits | No | Raise 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. | |
| strict | No | Escalate the first engine warning to a PKI_STRICT_DIAGNOSTIC error instead of listing it in diagnostics[]. | |
| pemMode | No | 'strict' (RFC 7468 exactly, default) or 'lax' (tolerates whitespace, line length and RFC 1421 headers). | strict |
| request | No | The 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. | |
| response | No | The 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. | |
| allowSha1 | No | Accept or produce SHA-1 signatures (legacy material you already trust). Default false: refused with PKI_CRYPTO_ALGORITHM_REFUSED on read, VALIDATION_ERROR on write. | |
| digestHex | No | The digest that was stamped, when you hold that rather than the data. Exclusive with data. Hexadecimal (an 0x prefix, colons and spaces are tolerated). | |
| verbosity | No | 'summary' keeps the scalar fields only (token-frugal). | full |
| candidates | No | Untrusted certificates the TSA's path may use (every PEM block of every item is taken). Optional; the certificates the token carries are also candidates. | |
| trustAnchors | Yes | Trust 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. | |
| encodingRules | No | 'der' (strict X.690 DER, default) or 'ber' (indefinite / non-minimal lengths and constructed strings, as legacy CMS producers write). | der |
| ocspResponses | No | OCSP 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. | |
| requireRevocation | No | Require 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. | |
| allowNonCriticalTimeStampingEku | No | Accept a TSA whose extKeyUsage names timeStamping but is not critical (RFC 3161 §2.3 requires it critical; some deployed TSAs are not). |
Output Schema
| Name | Required | Description |
|---|---|---|
| tsa | No | The TSA certificate whose key verified the token, or null. |
| chain | No | The chain judged by verify_certificate_chain, or null when there was no certificate to judge. |
| valid | No | Whether the token is evidence that the expected hash existed at genTime. false is a negative verdict (reasons[]), never an error. |
| latest | No | genTime plus the declared accuracy — the instant an LTV check compares against. |
| genTime | No | The time the TSA asserts (ISO 8601) — established only when valid. |
| reasons | No | Why 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. |
| earliest | No | genTime minus the declared accuracy. |
| diagnostics | No | Non-fatal concerns the engine raised (PKI_DIAG_*, de-duplicated, at most 200; explain_code explains one). |
| evaluatedAt | No | The ISO instant the TSA chain was judged at — re-pass it as `at` to reproduce the verdict. |
| reasonCodes | No | |
| diagnosticCount | No | |
| diagnosticCounts | No | Occurrences per diagnostic code. |
| evaluatedAtSource | No | |
| diagnosticsTruncated | No | true when distinct diagnostics beyond the 200 listed were dropped. |
| signatureVerifications | No |
TDQS
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.
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.
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.
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.
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.
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.
34 tool updates
v0.1.0- First observed
amend_cms - First observed
build_certificate_path - First observed
check_certificate_name - First observed
check_certificate_purpose - First observed
check_crl_revocation - First observed
check_ocsp_status - First observed
compute_fingerprint - First observed
convert_pem - First observed
create_certificate - First observed
create_csr - First observed
create_ocsp_request - First observed
create_timestamp_request - First observed
decode_asn1 - First observed
decode_extension - First observed
describe_engine - First observed
draft_governance_issue - First observed
encode_asn1 - First observed
encode_x509_structure - First observed
explain_code - First observed
inspect_certificate - First observed
inspect_cms - First observed
inspect_crl - First observed
inspect_csr - First observed
inspect_ocsp_response - First observed
inspect_pkcs12 - First observed
inspect_private_key - First observed
inspect_timestamp - First observed
open_pkcs12 - First observed
resolve_oid - First observed
sign_cms - First observed
validate_certificate_path - First observed
verify_certificate_chain - First observed
verify_cms - First observed
verify_timestamp
TDQS
Scored across 34 tools
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.
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.
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.
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
Related MCP Connectors
Production-grade cryptography toolkit with 31 MCP tools for classical, PQC, and KMS workflows.
Read-only verifier for 25 ProofRelay MCP tools and non-confidential evidence bundles.
238+ dev tools via MCP: JSON, QR, PDF, DNS, hash, UUID, code review, JWT, SSL, WHOIS, and more
49 developer tools via MCP: DNS, WHOIS, IP lookup, JWT, hashing, QR, and more.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProvides 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.2MIT
- AlicenseAqualityCmaintenanceMCP 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.445 npm6Apache 2.0
- AlicenseBqualityDmaintenanceDefense-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.182MIT

PKI Studio MCPofficial
AlicenseAqualityAmaintenanceA local MCP server for AI-assisted PKI and ASN.1 workflows, providing tools for certificate parsing, key management, and PKCS#12 processing.2827 npmMIT