How to Implement PGP Operations Using an MCP Server: A Complete Guide
To implement PGP operations using an MCP server, wrap a cryptographic library such as python-gnupg or openpgp in an HTTP service that exposes the mandatory /list and /call endpoints defined by the Model Context Protocol specification.
The punkpeye/awesome-mcp-servers repository catalogs reference implementations that demonstrate how to expose cryptographic functions to LLM agents. By following the architectural patterns established by the Cryptography section—specifically denismaggior8/enigma-python-mcp and laszlopere/mcp-bytesmith—you can build a stateless PGP service that enables AI systems to encrypt, decrypt, sign, and verify messages without compromising private key security.
Architecture Overview
A PGP-enabled MCP server requires three core components: a PGP library to handle cryptographic operations, MCP-compliant endpoints that expose tools to AI agents, and a secure key management strategy. The server operates as a local or containerized HTTP service that translates JSON-RPC requests from LLM clients into GnuPG function calls.
The pattern mirrors existing cryptography servers in the ecosystem. Both enigma-python-mcp and mcp-bytesmith demonstrate stateless, single-purpose architectures where each tool performs a specific transformation without external network dependencies.
Selecting a PGP Library
Choose a library based on your runtime environment and deployment constraints:
python-gnupg: A Python wrapper around the system GnuPG binary. Requires GnuPG installed on the host but provides full OpenPGP compatibility.pgpy: A pure-Python implementation requiring no external binaries. Ideal for containerized deployments where installing system dependencies is restricted.openpgp(Node.js): The standard npm package for JavaScript-based MCP servers.
For production MCP servers, pgpy eliminates the need to manage GnuPG home directories and binary dependencies, though python-gnupg offers broader algorithm support.
Implementing MCP Endpoints
Every MCP server must implement two mandatory HTTP endpoints that follow the protocol specification. A third endpoint for schema documentation is optional but recommended.
The List Endpoint
The /list endpoint returns a JSON array describing available PGP tools, their parameters, and return types. This allows LLM clients to discover capabilities dynamically.
{
"tools": [
{
"name": "pgp_encrypt",
"description": "Encrypt a plaintext message with a recipient's public key.",
"parameters": {
"type": "object",
"properties": {
"public_key": { "type": "string", "description": "ASCII-armored public key" },
"plaintext": { "type": "string", "description": "Message to encrypt" },
"armor": { "type": "boolean", "default": true }
},
"required": ["public_key", "plaintext"]
},
"returns": { "type": "string", "description": "ASCII-armored ciphertext" }
}
]
}
The Call Endpoint
The /call/<tool> endpoint executes the requested PGP operation. It accepts a JSON payload containing operation-specific arguments and returns the result or a structured error object.
Create analogous schemas for pgp_decrypt, pgp_sign, and pgp_verify, ensuring each specifies required fields such as passphrase for private key operations.
Defining PGP Tool Schemas
Structure your tool definitions to match the input requirements of your chosen library. For encryption, the schema must accept an ASCII-armored public key and plaintext, while decryption requires ciphertext and optional passphrase protection.
Encryption Handler Logic: Load the public key, then invoke gpg.encrypt(plaintext, pubkey, armor=True) to produce ciphertext.
Decryption Handler Logic: Pass the ciphertext and optional passphrase to gpg.decrypt(ciphertext, passphrase=priv) to recover the original message.
Signing Handler Logic: Use gpg.sign(message, keyid=keyid, detach=False) to create attached signatures, or set detach=True for separate signature files.
Verification Handler Logic: Call gpg.verify(signed_message) to return a verification object containing valid boolean status and key_id strings.
Complete Python Implementation
The following Flask-based implementation demonstrates a production-ready PGP MCP server using python-gnupg. This structure follows the stateless, local-only pattern established by denismaggior8/enigma-python-mcp.
# pgp_mcp.py - Core MCP server implementation
import json
import gnupg
import os
from flask import Flask, request, jsonify
app = Flask(__name__)
gpg = gnupg.GPG(gnupghome=os.getenv("GNUPG_HOME", "/tmp/.gnupg"))
@app.route("/list", methods=["GET"])
def list_tools():
"""Return available PGP tools per MCP specification."""
return jsonify({
"tools": ["pgp_encrypt", "pgp_decrypt", "pgp_sign", "pgp_verify"]
})
@app.route("/call/<tool>", methods=["POST"])
def call_tool(tool):
"""Execute requested PGP operation with error handling."""
data = request.get_json()
try:
if tool == "pgp_encrypt":
pub = data["public_key"]
pt = data["plaintext"]
cipher = gpg.encrypt(pt, pub, armor=data.get("armor", True))
return jsonify({"result": str(cipher)})
elif tool == "pgp_decrypt":
ct = data["ciphertext"]
priv = data.get("passphrase")
plain = gpg.decrypt(ct, passphrase=priv)
return jsonify({"result": str(plain)})
elif tool == "pgp_sign":
pt = data["message"]
key = data["key_id"]
signed = gpg.sign(pt, keyid=key, passphrase=data.get("passphrase"))
return jsonify({"result": str(signed)})
elif tool == "pgp_verify":
signed = data["signed_message"]
verified = gpg.verify(signed)
return jsonify({"valid": verified.valid, "key_id": verified.key_id})
else:
return jsonify({"error": "unknown tool"}), 400
except Exception as e:
return jsonify({"error": str(e)}), 500
if __name__ == "__main__":
app.run(port=3000)
Run the server locally with python pgp_mcp.py or containerize it for cloud deployment.
Security Considerations for Private Keys
Never expose private key material through the list or call endpoints. Store private keys in an encrypted filesystem or integrate with a secrets manager such as HashiCorp Vault, loading keys only at runtime into the GNUPG_HOME directory.
The server should operate with minimal privileges, and private key passphrases should only exist in memory during the specific decrypt or sign operation. Following the laszlopere/mcp-bytesmith model, avoid logging sensitive parameters or cryptographic material.
Deployment and Client Integration
Deploy the server locally for development using python -m pgp_mcp or build a Docker image for production environments. Register the server with the global MCP registry at registry.modelcontextprotocol.io to enable automatic discovery by AI agents.
LLM clients consume the service by calling the call endpoint with a JSON payload:
{
"tool": "pgp_encrypt",
"arguments": {
"public_key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...",
"plaintext": "Sensitive data for encryption",
"armor": true
}
}
The response contains ASCII-armored ciphertext that the LLM can forward to other agents or systems without accessing the plaintext content.
Summary
- Wrap existing libraries: Use
python-gnupg,pgpy, oropenpgpto handle OpenPGP operations without implementing cryptography from scratch. - Implement mandatory endpoints: Expose
/listfor tool discovery and/call/<tool>for operation execution, following patterns fromdenismaggior8/enigma-python-mcp. - Define strict schemas: Specify required parameters like
public_keyandplaintextfor encryption, and optionalpassphrasefields for private key operations. - Secure key management: Store private keys in encrypted storage or vaults, never exposing them through API responses or logs.
- Deploy statelessly: Run as a local service or containerized workload, avoiding external network dependencies to maintain security boundaries.
Frequently Asked Questions
What is the difference between using python-gnupg and pgpy for PGP operations in MCP servers?
python-gnupg requires a system GnuPG binary and home directory, offering full OpenPGP standard compliance and support for legacy key formats. pgpy is a pure-Python implementation that requires no external binaries, making it ideal for lightweight containers, though it may lack support for some specialized key types.
How do I secure private keys in an MCP server implementation?
Store private keys in an encrypted volume or integrate with a secrets manager like HashiCorp Vault. Mount keys into the server's GNUPG_HOME directory at runtime only, and ensure the application code never logs, caches, or returns private key material through the MCP endpoints.
Can an MCP server handle PGP operations without external GnuPG binaries?
Yes. By using pgpy instead of python-gnupg, you can implement encryption, decryption, signing, and verification entirely in Python without requiring the GnuPG binary or GNUPG_HOME directory. This approach mirrors the self-contained architecture of laszlopere/mcp-bytesmith.
How do I register my PGP MCP server for LLM client discovery?
Deploy your server and submit it to the MCP registry at registry.modelcontextprotocol.io. Clients like Claude Desktop or other MCP-aware agents query this registry to discover available tools, including your pgp_encrypt and pgp_decrypt operations, based on the schemas returned by your /list endpoint.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →