# How to Implement PGP Operations Using an MCP Server: A Complete Guide

> Implement PGP operations using an MCP server by wrapping crypto libraries in an HTTP service. Learn how to expose /list and /call endpoints with this complete guide.

- Repository: [Frank Fiegel/awesome-mcp-servers](https://github.com/punkpeye/awesome-mcp-servers)
- Tags: how-to-guide
- Published: 2026-09-02

---

**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.

```json
{
  "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`.

```python

# 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:

```json
{
  "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`, or `openpgp` to handle OpenPGP operations without implementing cryptography from scratch.
- **Implement mandatory endpoints**: Expose `/list` for tool discovery and `/call/<tool>` for operation execution, following patterns from `denismaggior8/enigma-python-mcp`.
- **Define strict schemas**: Specify required parameters like `public_key` and `plaintext` for encryption, and optional `passphrase` fields 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.