Back to Browse

Pkinative MCP Server

by Nizoka
Developer ToolsLow Risk9.5MCP RegistryLocal
Free

Server data from the Official MCP Registry

PKI MCP server: inspect, verify, issue, sign X.509, CSR, CRL, OCSP, CMS, TSP, PKCS#12. 34 tools

About

PKI MCP server: inspect, verify, issue, sign X.509, CSR, CRL, OCSP, CMS, TSP, PKCS#12. 34 tools

Security Report

9.5
Low Risk9.5Low Risk

Valid MCP server (1 strong, 2 medium validity signals). 1 known CVE in dependencies Package registry verified. Imported from the Official MCP Registry.

5 files analyzed · 2 issues found

Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.

Permissions Required

This plugin requests these system permissions. Most are normal for its category.

HTTP Network Access

Connects to external APIs or services over the internet.

env_vars

Check that this permission is expected for this type of plugin.

file_system

Check that this permission is expected for this type of plugin.

What You'll Need

Set these up before or after installing:

Absolute path of the one sandbox directory: path inputs are read from it and outputMode='file' outputs are written under it (never overwritten; the real path of every file read and of every parent written must stay inside — a planted symlink or junction is SECURITY_VIOLATION). Key-bearing files (.key .p8 .p12 .pfx) are read by the key tools only, never written, never served as resources. Unset: every tool works on inline PEM / base64 only.Optional

Environment variable: PKINATIVE_MCP_OUTPUT_DIR

Opt-in content-addressed response cache directory (SHA-256 keyed, 1h TTL, 256 MiB LRU, plaintext at rest). Never caches path inputs, file output, a call carrying a password or a signer, a clock-dependent instant, a random serial or nonce, describe_engine or draft_governance_issue.Optional

Environment variable: PKINATIVE_MCP_CACHE_DIR

Serve Streamable HTTP on this loopback port (POST /mcp) instead of stdio (MCP 2026-07-28, stateless, legacy fallback). Unauthenticated unless PKINATIVE_MCP_HTTP_TOKEN is set.Optional

Environment variable: PKINATIVE_MCP_PORT

Opt-in bearer token for the HTTP transport (>= 16 chars, no whitespace; a weaker value refuses to start). When set, /mcp requires 'Authorization: Bearer <token>' or answers 401. Compared constant-time, never logged.Required

Environment variable: PKINATIVE_MCP_HTTP_TOKEN

Operator-side password of the encrypted PKCS#8 key or PKCS#12 file the tools open (keeps the secret out of the model transcript). Exclusive with the per-call password input (both at once is VALIDATION_ERROR). Never logged; only its presence is reported at startup.Required

Environment variable: PKINATIVE_MCP_PASSWORD

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

Environment variable: PKINATIVE_MCP_AT

Operator ceiling for limits.maxInputBytes and the decoded size of one PKI object (default 67108864 = 64 MiB, the engine default). Integer >= 1024, read once at startup; an invalid value refuses to start. A per-call value above it is refused with LIMIT_CEILING_EXCEEDED.Optional

Environment variable: PKINATIVE_MCP_MAX_INPUT_BYTES

Operator ceiling for limits.maxKdfIterations and limits.maxPkcs12KdfIterations (default 10000000, the engine default): the CPU bound of a hostile PBES2 key or PKCS#12 file. Integer >= 1, read once at startup; an invalid value refuses to start.Optional

Environment variable: PKINATIVE_MCP_MAX_KDF_ITERATIONS

Operator ceiling for limits.maxPathsExplored (default 1000, the engine default): the RFC 5280 section 6 path-building work bound. Integer >= 1, read once at startup; an invalid value refuses to start.Optional

Environment variable: PKINATIVE_MCP_MAX_PATHS_EXPLORED

How to Install

Add this to your MCP configuration file:

{
  "mcpServers": {
    "io-github-nizoka-pkinative-mcp": {
      "env": {
        "PKINATIVE_MCP_AT": "your-pkinative-mcp-at-here",
        "PKINATIVE_MCP_PORT": "your-pkinative-mcp-port-here",
        "PKINATIVE_MCP_PASSWORD": "your-pkinative-mcp-password-here",
        "PKINATIVE_MCP_CACHE_DIR": "your-pkinative-mcp-cache-dir-here",
        "PKINATIVE_MCP_HTTP_TOKEN": "your-pkinative-mcp-http-token-here",
        "PKINATIVE_MCP_OUTPUT_DIR": "your-pkinative-mcp-output-dir-here",
        "PKINATIVE_MCP_MAX_INPUT_BYTES": "your-pkinative-mcp-max-input-bytes-here",
        "PKINATIVE_MCP_MAX_KDF_ITERATIONS": "your-pkinative-mcp-max-kdf-iterations-here",
        "PKINATIVE_MCP_MAX_PATHS_EXPLORED": "your-pkinative-mcp-max-paths-explored-here"
      },
      "args": [
        "-y",
        "pkinative-mcp",
        "-y",
        "pkinative-mcp"
      ],
      "command": "npx"
    }
  }
}

Documentation

View on GitHub

From the project's GitHub README.

pkinative-mcp

MCP server for PKI — inspect and verify X.509 certificates, CSRs, CRLs, OCSP responses, CMS SignedData and RFC 3161 time-stamps; build and validate certificate chains (RFC 5280 path validation, name and purpose checks, offline CRL and OCSP revocation); create certificates, CSRs, CMS signatures, OCSP and time-stamp requests; open PKCS#8 and PKCS#12 without ever returning a key byte; decode ASN.1, PEM and OIDs — 34 tools and 7 prompts on the pkinative engine (zero-dependency, pure TypeScript, Web Crypto for every signature; OpenSSL 3 interoperability validated in CI), for Claude Desktop, Cursor, ChatGPT and any Model Context Protocol client. No network, ever.

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


✨ Features

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

Encodings

ToolKindWhat it doesKey refusal codes
convert_pemreadmode: 'decode' lists every block of a PEM text (label, DER size, offset, SHA-256) and extractIndex delivers one as DER / PEM / hex; mode: 'encode' wraps exactly one DER value into one strict RFC 7468 block under label. A private-key block is listed, never extracted and never produced; key material anywhere in the input is refused.SECURITY_VIOLATION, NOT_FOUND, VALIDATION_ERROR, PKI_PEM_UNEXPECTED_LABEL, PKI_PEM_*
resolve_oidreadObject identifiers in one call: name (registered names, ≤ 1000 per call), validate (X.660 grammar, reported never thrown), list (the engine registry, paged 200 / 2000), encode (dotted → hex, content or whole TLV, RELATIVE-OID), decode (hex → dotted + name).VALIDATION_ERROR, PKI_OID_INVALID, PKI_ASN1_*
compute_fingerprintreadfingerprint of the exact DER (SHA-256 default; SHA-1 / 384 / 512), key-id (the RFC 5280 §4.2.1.2 identifier of the public key of a certificate, CSR or PUBLIC KEY), shake256 (outputLength bytes, the Ed448 CMS digest). Any object may be digested; no input byte is returned.VALIDATION_ERROR, PKI_X509_*, PKI_ASN1_*
decode_asn1readThe X.690 node tree of any DER (BER with encodingRules: 'ber'): offsets, tags, previews, OID names, ≤ 20 000 nodes; nodePath narrows to a subtree, read applies a typed reader, reencode: true re-encodes the node as an artefact. Refuses a private key or a PKCS#12 (by label, or when the DER parses as one).SECURITY_VIOLATION, NOT_FOUND, PKI_ASN1_UNEXPECTED_TAG, PKI_API_MISUSE, PKI_ASN1_*
encode_asn1artefactA JSON node spec (boolean, integer, bit-string, octet-string, oid, string, time, sequence / set / set-of, explicit / implicit, tlv, der) encoded to DER through the engine's own encoders — deterministic; the inverse of decode_asn1.VALIDATION_ERROR (naming the JSON path), SECURITY_VIOLATION, PKI_LIMIT_EXCEEDED, PKI_ASN1_*, PKI_OID_INVALID

Certificates and requests

ToolKindWhat it doesKey refusal codes
inspect_certificatereadThe RFC 5280 report of one certificate: serial, algorithms, issuer / subject (RFC 4514 + RDNs), validity, public key, the 19 extension kinds decoded (rawExtensions: true keeps them raw), fingerprints, diagnostics; extension returns one; issuer / checkSelfSignature adds one signature check — which is not trust.NOT_FOUND, PKI_PEM_UNEXPECTED_LABEL, PKI_X509_*, PKI_CRYPTO_ALGORITHM_REFUSED
create_certificateartefactIssue an X.509 v3 certificate from spec (subject, validity, extensions), signed by signer (a PKCS#8 key or a PKCS#12 file imported as a non-extractable Web Crypto key): self-signed CA, or a leaf issued from a certificate / CSR / PUBLIC KEY. Serial 'random' by default; verified against the issuer before the bytes return.KEY_MISMATCH, RSA_SCHEME_REQUIRED, KEY_TYPE_REQUIRED, SECRET_REQUIRED, UNSUPPORTED_KEY, VALIDATION_ERROR
encode_x509_structureartefactOne X.509 building block (name, validity, spki, algorithm-identifier, extension(s), basic-constraints, key-usage, extended-key-usage, subject-alt-name, SKI / AKI, signature-algorithm) encoded deterministically from a closed spec; hex by default. Nothing here signs.VALIDATION_ERROR (naming the member), KEY_TYPE_REQUIRED, SECRET_REQUIRED, RSA_SCHEME_REQUIRED
decode_extensionreadOne extnValue content (hex or DER) decoded by its OID exactly as inspect_certificate would; critical is checked against the kind's RFC 5280 rule (a diagnostic, not a refusal).PKI_X509_EXTENSION_MALFORMED, VALIDATION_ERROR
check_certificate_nameverdictThe RFC 6125 identity verdict: certificate + host / ip (SAN dNSName / iPAddress, wildcards, an opt-in CN fallback held to the chain's name constraints), or the match form presented + reference with no certificate.PKI_REASON_NAME_MISMATCH (in reasons[]), VALIDATION_ERROR
check_certificate_purposeverdictMay certificate be used for purpose (serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, ocspSigning, any, or an OID) along [certificate, ...chain]? restrictIssuers (default true) applies a CA's own EKU to what it issued.PKI_REASON_PURPOSE_NOT_PERMITTED (in reasons[]), VALIDATION_ERROR
inspect_csrreadThe RFC 2986 report of one PKCS#10 request: subject, key, attributes, the extensions requested, whether a challengePassword is present (never its text), and by default the proof of possession (signature.valid).PKI_REASON_SIGNATURE_NOT_CHECKED (in reasons[]), PKI_X509_*, PKI_PEM_UNEXPECTED_LABEL
create_csrartefactA PKCS#10 request from spec (subject, the extensions to request) signed by signer; the public key is derived from a plain key, taken from the PKCS#12 certificate or given as subjectPublicKey; the proof of possession is verified before the bytes return.KEY_MISMATCH, RSA_SCHEME_REQUIRED, SECRET_REQUIRED, KEY_TYPE_REQUIRED, VALIDATION_ERROR

Paths, CRLs and OCSP

ToolKindWhat it doesKey refusal codes
verify_certificate_chainverdictThe one-call trust verdict: a path from leaf through candidates to one of your trustAnchors, every signature verified, RFC 5280 §6 validation at at (validity, constraints, key usage, names, policies), serverName (RFC 6125) and purposes, and revocation from the CRL / OCSP bytes you supply (requireRevocation demands evidence for the end-entity certificate).PKI_REASON_* in reasons[] (NO_TRUST_ANCHOR, EXPIRED, NOT_YET_VALID, REVOKED, REVOCATION_UNKNOWN, NAME_MISMATCH, …), VALIDATION_ERROR
build_certificate_pathverdictPath discovery only: which intermediates lead from leaf to an anchor under the structural rules at at. No signature is checked — valid is always false, the verdict is found and structuralReasons.PKI_REASON_SIGNATURE_NOT_CHECKED (always), PKI_REASON_NO_TRUST_ANCHOR
validate_certificate_pathverdictRFC 5280 §6.1 validation of a path you ordered (leaf first) against your anchors at at; every link signature verified first (checkSignatures: false gives the structural verdict with failingReasons). No revocation here.PKI_REASON_SIGNATURE_INVALID, PKI_REASON_SIGNATURE_NOT_CHECKED, VALIDATION_ERROR
inspect_crlreadThe RFC 5280 §5 report of one CRL: issuer, updates, CRL number, delta indicator, IDP, entry count (walked, never all decoded), extensions; find looks one serial up; issuer adds one signature check.VALIDATION_ERROR, PKI_X509_*, PKI_CRYPTO_ALGORITHM_REFUSED
check_crl_revocationverdictOne certificate against one base crl (and an optional delta) you fetched, at at: scope, freshness (staleTolerance), the signature verified against issuer, the serial looked up. A delta whose signature does not verify against issuer is not applied: valid: false with PKI_REASON_REVOCATION_UNKNOWN on delta.PKI_REASON_REVOKED, PKI_REASON_REVOCATION_UNKNOWN (in reasons[]), VALIDATION_ERROR (a delta without deltaCRLIndicator)
create_ocsp_requestartefactThe DER OCSPRequest (RFC 6960) for certificate under issuer: CertID with SHA-1 (default) or SHA-256, an RFC 8954 nonce ('random' or hex), certIdOnly. You POST the bytes to the responder; summary.nonceHex / certIdHex match the answer.VALIDATION_ERROR
inspect_ocsp_responsereadThe RFC 6960 report of one response: status, responder id, producedAt, every SingleResponse (CertID, status, updates), extensions, embedded certificates, nonce presence; responder adds one signature check.PKI_* (PKI_ASN1_*, PKI_X509_*), VALIDATION_ERROR
check_ocsp_statusverdictOne response for one certificate: the CertID recomputed from issuer, the signature verified (responder, the embedded certificates or the issuer), the signer authorised (RFC 6960 §4.2.2.2), status, freshness at at, nonce (nonce, requireNonce).PKI_REASON_REVOKED and the other PKI_REASON_* in reasons[], VALIDATION_ERROR

CMS and time-stamps

ToolKindWhat it doesKey refusal codes
sign_cmsartefactAn RFC 5652 SignedData over content (attached or detached) or a precomputed contentDigest, one signer (PKCS#8 key or PKCS#12), certificate, chain and crls parsed then embedded; signed attributes always written (contentType, messageDigest, signingTime, SigningCertificateV2, CMSAlgorithmProtection); verified against the signer certificate before the bytes return.KEY_MISMATCH, RSA_SCHEME_REQUIRED, SECRET_REQUIRED, VALIDATION_ERROR, PKI_API_MISUSE, ENGINE_ERROR
verify_cmsverdictThe one-call verdict on a SignedData: for every signer the signed attributes, the content digest (attached, or content / contentDigest), the signature, the chain to your trustAnchors at at, every counter time-stamp, and revocation from the bytes you supply plus those the message carries; atTimeStamp: true is long-term validation.PKI_REASON_* in reasons[] (per signer too; intact tells unaltered from untrusted), PKI_ASN1_TRAILING_DATA, VALIDATION_ERROR
inspect_cmsreadThe RFC 5652 report: content type and length, digest algorithms, embedded certificates, CRL / OCSP counts, every signer (identifier, algorithms, signing time as claimed, attributes, time-stamp count, the signature value a counter time-stamp stamps); signerCertificate adds one key check on the signer signerIndex names.NOT_FOUND (a signerIndex past the last signer, with signerCertificate), PKI_ASN1_TRAILING_DATA, PKI_*
amend_cmsartefactThe one edit a signed message legitimately receives afterwards: add-unsigned-attribute (a DER Attribute as hex) or add-timestamp (a TimeStampToken or the whole TimeStampResp) on signer signerIndex, every signed octet unchanged; the token's imprint is checked against the signature value before anything is written.VALIDATION_ERROR (data.detail PKI_REASON_TSP_IMPRINT_MISMATCH), NOT_FOUND, PKI_API_MISUSE
create_timestamp_requestartefactThe RFC 3161 TimeStampReq you POST to a TSA yourself: imprint over data or digestHex (SHA-256 / 384 / 512), nonce ('random', decimal or hex), policy, certReq.VALIDATION_ERROR (a digest of the wrong length)
inspect_timestampreadA TimeStampResp, a TimeStampToken or a bare TSTInfo (auto-detected, or as): status, policy, imprint, serial, genTime, accuracy, nonce, the TSA name as stated, the CMS envelope. Parsing proves nothing.PKI_*, PKI_STRICT_DIAGNOSTIC (with strict: true)
verify_timestampverdicttoken or response judged against what you say was stamped (request, data or digestHex — at least one), the TSA signature, its chain to your anchors at at with a critical timeStamping EKU, and revocation from the bytes you supply.PKI_REASON_TSP_IMPRINT_MISMATCH, PKI_REASON_TSP_NOT_GRANTED and the other PKI_REASON_* in reasons[], VALIDATION_ERROR

Private keys and PKCS#12

ToolKindWhat it doesKey refusal codes
inspect_private_keyreadDescribe a PKCS#8 key without its password (with PKINATIVE_MCP_PASSWORD unset — the variable counts as the password of every password-accepting tool): format, kind, curve, algorithm, the PBES2 parameters of an encrypted key (or the legacy scheme pkinative refuses); tryImport: true imports it as a non-extractable Web Crypto key and reports the handle. No result ever carries a key byte.SECRET_REQUIRED, KEY_TYPE_REQUIRED, RSA_SCHEME_REQUIRED, PKI_CRYPTO_DECRYPTION_FAILED, PKI_KEY_ENCRYPTION_UNSUPPORTED
inspect_pkcs12readDescribe a .p12 / .pfx without its password (the operator's PKINATIVE_MCP_PASSWORD counts as one, so it must be unset): MAC construction (PBMAC1, or the legacy RFC 7292 MAC), every SafeContents and its scheme, the bags of the plain ones; with a password, macVerified and opened (allow-list views of every bag) — a wrong password on an encrypted SafeContents is PKI_CRYPTO_DECRYPTION_FAILED.PKI_CRYPTO_DECRYPTION_FAILED, PKI_KEY_MAC_UNSUPPORTED, PKI_KEY_ENCRYPTION_UNSUPPORTED
open_pkcs12verdictOpen a PKCS#12 in one call: MAC verified, PBES2 SafeContents decrypted, every key unwrapped straight into a non-extractable Web Crypto key (described, never exported), certificates parsed, CRLs counted; exportCertificates: true attaches a PEM bundle of the certificates and CRLs — never a key.SECRET_REQUIRED; in reasons[]: PKI_REASON_PKCS12_MAC_MISMATCH, PKI_REASON_PKCS12_ENCRYPTION_UNSUPPORTED, PKI_REASON_PKCS12_INTEGRITY_UNVERIFIED, PKI_REASON_PKCS12_RSA_SCHEME_UNSPECIFIED

Meta

ToolKindWhat it doesKey refusal codes
describe_engineengineOffline preflight: versions, the host's Web Crypto capabilities, the engine's 22 default limits, the operator ceilings, every server cap, sandbox / cache / pinned-instant state, the 57 PKI_* / 43 PKI_REASON_* / 97 PKI_DIAG_* vocabularies, the 21 wrapper codes, the doctrine refusals and the deliberately unexposed engine surface. network is always 'none'. Never cached.VALIDATION_ERROR
explain_codeexplainAny code this server can emit, explained from the registries vendored at the pkinative v1.0.0 tag (class, standard, CWE, remedy, the MCP input that lifts it); list: true enumerates one vocabulary.NOT_FOUND
draft_governance_issuegovernanceA local GitHub issue draft plus a compliance report for pkinative, pkinative-cli or pkinative-mcp — never submitted; a human files it under their own identity.GOVERNANCE_VIOLATION, MISSING_OUTPUT_PATH, INVALID_EXTENSION, OUTPUT_EXISTS, SECURITY_VIOLATION

What every tool guarantees:

  • 🌐 No network, ever — no CRL / OCSP / AIA / TSA fetch, no telemetry, no GitHub, no URL from any argument, no operator-configurable endpoint. Revocation evidence and time-stamp tokens are inputs; create_ocsp_request and create_timestamp_request produce bytes you transport. Every tool advertises openWorldHint: false.
  • 🔑 No key byte leaves — signing keys are non-extractable Web Crypto handles; every key / PKCS#12 report is an allow-list view; key-bearing files (.key .p8 .p12 .pfx) are never written and never served as resources; convert_pem, decode_asn1 and encode_asn1 refuse private-key material. There is no key generation and no key export.
  • 🧭 Verdicts are reports, errors are refusals — a negative verdict is valid: false + reasons[] with the engine's PKI_REASON_* codes (43 of them); isError is reserved for an input the engine refuses (one of the 57 PKI_* codes, passed through verbatim with data.errorClass and a remedy) or a wrapper refusal (21 codes, each with a remedy). Branch on the code, never on the message.
  • 📐 Doctrine, surfaced not hidden — SHA-1 only with allowSha1: true; RSA needs an explicit rsaScheme ('pkcs1' | 'pss', no default); PBES2 and PBMAC1 only (a legacy RC2 / 3DES PKCS#12 is refused with the OpenSSL 3.4 conversion remedy); DSA and ML-DSA are parsed, never verified; diagnostics (97 PKI_DIAG_* codes) are not errors unless strict: true; the engine's 22 limits raise per call and never above the operator ceilings.
  • 🪙 Token-frugal — read and verdict tools accept verbosity: 'summary' and fields: [...]; artefacts arrive once as an embedded resource block or a resource_link, never duplicated into structuredContent.
  • 🔁 Reproducible on request — pin at, notBefore, signingTime, serialNumber and nonce (or let the operator pin the instant with PKINATIVE_MCP_AT) and an artefact or a verdict is a pure function of its inputs; structuredContent.sha256 is the proof (docs/guides/REPRODUCIBLE.md).
  • 🧪 OpenSSL 3 is the judge — every artefact the server writes is read back by OpenSSL 3.x in CI (23 conformant artefacts, 5 refused canaries, 12 command families), blocking on Linux and Windows.

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

  • base64 (default) — the artefact is returned once as an embedded resource content block (PEM as text, DER as blob, with its MIME type: application/pkix-cert, application/pkcs10, application/pkcs7-signature, application/pkix-crl, application/ocsp-request, application/timestamp-query, …); structuredContent carries { mode, encoding, sizeBytes, sha256, summary, diagnostics } — with encoding: 'hex' the bytes land in structuredContent.hex instead (inline only, ≤ 64 KiB).
  • file — the artefact is written inside the sandbox configured by PKINATIVE_MCP_OUTPUT_DIR (exclusive create, never overwritten → OUTPUT_EXISTS; ≤ 1 GiB) and the result carries a resource_link. File I/O is disabled unless this variable is set; absolute paths, traversal, NUL bytes, a key-bearing or unknown extension and a symlink or junction whose real path leaves the sandbox are all rejected. The same sandbox serves every path input, so a create_csr → create_certificate → verify_certificate_chain chain never re-sends the bytes.

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

  • verbosity: 'summary' — a compact scalar-only verdict. E.g. verify_certificate_chain → { valid, reasonCodes, pathLength, evaluatedAt, revocationEvidence, diagnosticCount }; inspect_certificate → subject, issuer, serial, validity, key, extension kinds.
  • fields: ['a', 'b.c'] — projects the structured result to named dot-paths (≤ 16; arrays map over elements); composes after verbosity. Every path that matched nothing — nested paths included — is omitted and reported in _meta.unmatchedFields (with _meta.availableFields); a path holding a __proto__, constructor or prototype segment is VALIDATION_ERROR.

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

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

Why pkinative?

pkinative-mcp inherits every guarantee of the underlying engine:

  • Zero runtime dependencies in the engine — pure TypeScript, no native bindings; Web Crypto for every signature, so no secret-dependent arithmetic runs in JavaScript (this server adds only the MCP SDK and zod: three runtime dependencies in total).
  • RFC 5280 path validation (building, §6.1 validation, name constraints, policies), RFC 6125 names, RFC 6960 OCSP, RFC 5652 CMS, RFC 3161 time-stamps, RFC 2986 CSRs, RFC 5958 / 8018 PKCS#8 with PBES2, RFC 7292 / 9579 PKCS#12 with PBMAC1, strict X.690 DER with opt-in BER.
  • 22 named, CWE-tagged limits on every parser loop — raised only by explicit input, never silently.
  • A frozen vocabulary — 57 PKI_* error codes, 43 PKI_REASON_* verdict reasons and 97 PKI_DIAG_* diagnostics, vendored at the v1.0.0 tag and passed through verbatim.
  • What it will NOT do: no key generation, no key export, no writer for PKCS#8 / PKCS#12 / CRL / OCSP response / time-stamp response, no DSA / ML-DSA verification, no network access, ever.

🚀 Installation

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

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

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


⚙️ Configuration

Claude Desktop

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

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

Claude Code

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

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

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

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

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

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

VS Code

.vscode/mcp.json:

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

🌐 Supported AI Ecosystem & Clients

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

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

🔌 MCP protocol compliance

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

  • Stateless serving — server/discover replaces the session handshake; every result carries resultType and the _meta serverInfo envelope. Over HTTP, 2026-07-28 clients send Mcp-Method / Mcp-Name headers with each POST /mcp.
  • Cache hints — tools/list and prompts/list are public with a 24 h ttlMs, server/discover is public for 1 h, and resources/list / resources/templates/list / resources/read are private with ttlMs: 0 (sandbox files are per-host user data).
  • Protocol errors — tools/call with an unknown tool name is a JSON-RPC error (-32602, [UNKNOWN_TOOL] Unknown tool: …) rather than an isError result, and an unknown resource URI is -32602 (Invalid params), as the specification requires; isError: true is reserved for execution failures.
  • Automatic legacy fallback — a client that opens with initialize is served through the SDK's legacy path on both stdio and HTTP for every revision the SDK negotiates: 2024-10-07, 2024-11-05, 2025-03-26, 2025-06-18 and 2025-11-25 (legacy) next to 2026-07-28 (modern). Nothing changes for existing hosts.
  • HTTP — POST /mcp only; GET / DELETE /mcp answer 405 (no SSE resumability; the server is stateless). Bound to 127.0.0.1 with a Host / Origin guard (foreign values → 403); PKINATIVE_MCP_HTTP_TOKEN adds an opt-in bearer-token gate (401 + WWW-Authenticate without it); bodies above 256 MiB → 413.
  • stdio — the frame cap is 256 MiB so a multi-MiB base64 CMS message or PKCS#12 fits in one tools/call; as in every SDK release, a request sent before initialize is dropped without a reply on the legacy path.
  • Output schemas — every structuredContent validates against the tool's outputSchema (a 2026-07-28 MUST), including verbosity: 'summary' and fields projections: the read and verdict tools declare projectable schemas (all properties optional, additionalProperties: false kept). Input schemas carry no $schema keyword and no $ref by policy (some hosts forward inputSchema to function-calling APIs that reject unknown keywords). serverInfo carries websiteUrl; the resource template is pkinative://output/{+path}.

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

ClientTransportProtocol negotiated
Claude Desktop, Cursor, Continue, Zed, Windsurf, Clinestdiolegacy initialize (2024-xx / 2025-xx) — unchanged
ChatGPT and other Streamable HTTP hostsHTTP POST /mcplegacy stateless streamable HTTP — unchanged
MCP 2026-07-28 clients (SDK v2 Client, current MCP Inspector)stdio / HTTPserver/discover, cache hints, _meta envelope

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

RevisionErastdioHTTP POST /mcpProven by
2024-10-07legacy initialize✓✓tests/protocol-revisions.test.ts
2024-11-05legacy initialize✓✓tests/protocol-revisions.test.ts
2025-03-26legacy initialize✓✓tests/protocol-revisions.test.ts
2025-06-18legacy initialize✓✓tests/protocol-revisions.test.ts, tests/http-transport.test.ts
2025-11-25legacy initialize✓✓tests/protocol-revisions.test.ts, tests/cli-stdio.test.ts, tests/autonomy.test.ts
2026-07-28modern (server/discover, _meta envelope, cache hints)✓✓ (+ bearer token)tests/stdio-modern.test.ts, tests/http-modern.test.ts, tests/autonomy.test.ts

Environment variables

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

VariableDefaultMeaning
PKINATIVE_MCP_OUTPUT_DIR(unset)Absolute path of the one sandbox directory: every path input (PKI objects, content, keys) is read from it and outputMode: 'file' outputs are written under it (exclusive create — never overwritten). The real path of every file read and of every parent written must stay inside it: a symlink or junction planted in the sandbox is SECURITY_VIOLATION on both sides. Key-bearing files (.key .p8 .p12 .pfx) are read by the key tools only, never written, never served as resources — and resources/* refuse key material by content too, whatever the name. Unset ⇒ every tool works on inline PEM / base64 only.
PKINATIVE_MCP_CACHE_DIR(unset)Opt-in content-addressed response cache (SHA-256 keyed, 1 h TTL, 256 MiB LRU, plaintext at rest; key namespaced by tool API, package and engine version). Never caches a path input, file output, a call carrying a password or a signer, a 'random' serial or nonce, a 'now' or unpinned instant, describe_engine or draft_governance_issue — nor any password-accepting tool while PKINATIVE_MCP_PASSWORD is set; a hit carries _meta.cached: true.
PKINATIVE_MCP_PORT(unset → stdio)When set to a valid port (1–65535), serves Streamable HTTP on http://127.0.0.1:<port>/mcp instead of stdio; any other value refuses to start. Request bodies up to 256 MiB are accepted (413 above). Loopback only, DNS-rebinding protection (foreign Host / Origin → 403), GET / DELETE → 405. No authentication unless PKINATIVE_MCP_HTTP_TOKEN is set — other local processes can reach the endpoint.
PKINATIVE_MCP_HTTP_TOKEN(unset)(secret) Opt-in bearer token for the HTTP transport (≥ 16 characters, no whitespace — a weaker value aborts startup). When set, every /mcp request must carry Authorization: Bearer <token>; otherwise 401 + WWW-Authenticate: Bearer realm="pkinative-mcp" (with error="invalid_token" only when credentials were sent — RFC 6750 §3.1). Compared constant-time, never logged.
PKINATIVE_MCP_PASSWORD(unset)(secret) The operator-side password of the encrypted PKCS#8 key or PKCS#12 file the tools open — it keeps the secret out of the model transcript. It counts as the password of every password-accepting tool (inspect_private_key, inspect_pkcs12, open_pkcs12, the signer of the signing tools): "describe without a password" needs it unset. Exclusive with the per-call password input (both at once is VALIDATION_ERROR). Never logged; only its presence is reported at startup.
PKINATIVE_MCP_AT(unset → wall clock)Pinned instant (ISO 8601, or epoch milliseconds) every omitted at / notBefore / signingTime falls back to, for reproducible verdicts and artefacts. Unset: the wall clock — and a clock-dependent result is never cached.
PKINATIVE_MCP_MAX_INPUT_BYTES67108864 (64 MiB)Operator ceiling for limits.maxInputBytes and the decoded size of one PKI object (the engine default). Integer ≥ 1024. A per-call value above it is LIMIT_CEILING_EXCEEDED; a ceiling below the engine default tightens the default silently.
PKINATIVE_MCP_MAX_KDF_ITERATIONS10000000Operator ceiling for limits.maxKdfIterations and limits.maxPkcs12KdfIterations (the engine default): the CPU bound of a hostile PBES2 key or PKCS#12 file. Integer ≥ 1.
PKINATIVE_MCP_MAX_PATHS_EXPLORED1000Operator ceiling for limits.maxPathsExplored (the engine default): the RFC 5280 §6 path-building work bound. Integer ≥ 1.

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


🛠 Tool reference

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

convert_pem

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

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

resolve_oid

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

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

compute_fingerprint

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

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

decode_asn1

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

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

encode_asn1

Documentation truncated — see the full README on GitHub.

Reviews

No reviews yet

Be the first to review this server!