
gpg-mcp
io.github.emiliano-go由 GitHub 帳號 emiliano-go 發布v0.1.0更新於 Oct 27, 2026
Security-focused MCP server for GnuPG signing, encryption and key management.
概覽
面向 GnuPG 的本機 MCP 伺服器,讓助理在不接觸私鑰材料的情況下完成 OpenPGP 簽章、驗簽、加解密與金鑰管理。
- 可以做什麼
- 列出、檢查並匯出公鑰、私鑰中繼資料、SSH 公鑰與智慧卡狀態
- 驗證分離式、明文與內嵌簽章,並區分密碼學結果與政策結果
- 透過允許清單中的金鑰進行簽章、加密、簽章加密與解密,可選對稱式加密
- 建立金鑰、新增子鑰與 UID、設定到期日、變更通行碼、匯入公鑰與設定信任度
- 刪除或撤銷金鑰、匯入撤銷憑證,需手動輸入指紋後綴確認
- 可選在設定的金鑰伺服器上搜尋、取得與發布公鑰
- 適用情境
- 適合在 Unix 機器上讓助理處理 OpenPGP 簽章或加密工作,例如簽署發布產物或解密訊息,同時把私鑰留在 gpg-agent 中,並要求高風險操作經人工核准。
- 執行需求
- 以 PyPI 套件形式透過 stdio 在本機執行(uvx gpg-mcp serve)。需要 Python 3.11+、GnuPG 2.2+(建議 2.4)、pinentry 程式,approval 設為 local 時還需 zenity。僅支援 Linux 等類 Unix 系統,不支援 Windows。可用 GPG_MCP_CONFIG 或 --config 指定設定檔路徑,首次執行需先跑 init 與 doctor。
安裝
在 SourceWeft 中
- 開啟 儀表板中的 gpg-mcp,將其新增到工作區。
- 為需要使用其工具的對話啟用該服務。
Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。
其他 MCP 客戶端
參照 儲存庫 中的啟動說明。
README
gpg-mcp
A security-focused Model Context Protocol server for GnuPG.
gpg-mcp lets an AI agent sign, verify, encrypt, decrypt and manage OpenPGP keys without ever
receiving private key material. Private keys stay in gpg-agent; passphrases go only into
pinentry; high-risk operations require an explicit human approval; destructive operations require
typing a fingerprint suffix.
Threat model in one sentence: the MCP server is a policy boundary, not an OS sandbox. It prevents the agent from receiving private keys and from silently using them. For protection against an agent that already has shell access as the same OS user, run the signer as a separate user or use a hardware token (see Stronger isolation).
Why this exists
Most “GPG MCP” wrappers are thin subprocess shells: any tool with a command: str parameter, or
an agent that can ask for --export-secret-keys, defeats the point. gpg-mcp instead exposes a
fixed, typed surface with deliberate omissions:
- No raw command execution. There is no
run_gpg(args)tool. - No secret-key export or backup.
--export-secret-keys,--export-secret-subkeysand--export-secret-ssh-keyare unreachable. - No passphrase parameters. No
--passphrase,--passphrase-fd,--passphrase-file, no loopback pinentry, no passphrase in environment variables or stdin. - No
--homediroverride. The keyring is fixed by configuration. - No short key IDs. Operations require full 40- or 64-hex fingerprints.
- No automatic key retrieval. Verification does not silently fetch keys from the network.
- No hidden recipients. Decryption preflights the ciphertext and refuses messages not addressed to an allowed key.
- A response guard. If gpg ever emits something that looks like private key material, the call fails and the output is dropped.
Security model
What the server guarantees
- Private keys never cross the MCP boundary. Listing secret keys returns metadata only.
Signing and decryption are performed by
gpg-agent. - The server never sees a passphrase.
--pinentry-mode askis forced; pinentry talks to the human directly. The server cannot read the passphrase from argv, env or stdin. - Signing and decryption use allow-listed keys. With a shared
~/.gnupg, an empty allow-list denies both. With a dedicated keyring, the keyring itself is the allow-list boundary. - Decryption is recipient-checked. Before decrypting,
gpg --list-packetsis used to confirm the ciphertext is addressed to an allowed key. Hidden recipients are refused by default, even when the same message also has a visible allowed recipient. - Invalid embedded signatures abort decryption. If
verify=true(default) and the decrypted message carries a bad, expired or revoked signature, the plaintext is not returned.validis the policy result; the cryptographic result is reported separately. Multiple signatures are aggregated perNEWSIGsequence — or per packet on GnuPG without--proc-all-sigs— and fail closed if any signature is not policy-acceptable, regardless of event order. - Authorization is a fail-closed registry. Every high-risk tool names a registered operation.
Unknown operations require approval. Composite operations (
sign_and_encrypt, andencryptwithsign_with) inherit the approval requirement of every constituent, so a policy that gates only signing or only encryption cannot be bypassed. - Revocation certificates are validated before import. The certificate must contain a
revocation signature, must reject secret-key packets, must target exactly the requested
fingerprint, and must demonstrably revoke that key in an ephemeral keyring.
apply=falseis a true dry run and never touches the real keyring. - Destructive operations require typed confirmation. Deleting, revoking, or applying a
revocation certificate requires the operator to type the last 8 characters of the fingerprint.
This is controlled by
destructive_confirmationand is independent ofrequire_approval. - Every high-risk operation is audited. The ledger records operation names, fingerprints, payload SHA-256, size and the decision — never plaintext, payload previews or passphrases.
- No shell. Commands are
execve-style argv arrays. No shell interpolation, no option injection from user strings. - Minimal environment.
LD_*,GPG_AGENT_INFO,GPG_TTY,SSH_AUTH_SOCKand other variables are not passed to gpg. Only the display/session variables pinentry needs are kept. - Sanitised binaries and paths.
gpg,gpgconfandzenityare resolved to absolute paths, must be regular files, and must not be group/world writable. An unsafezenitymakes local approval fail closed instead of trusting a substituted dialog. - Bounded resources. Input/output size caps, per-operation timeouts, a dedicated process group killed on timeout, and a 0700 temporary directory with 0600 files. Status output beyond the effective status limit fails closed instead of being parsed partially.
- The generic importer cannot revoke.
import_public_keyrejects secret-key packets and any revocation signature, including revocation signatures embedded in key material. Revocations must useimport_revocation, where the target is validated and typed confirmation is required. - The actual decryption key is checked. After decryption, the
DECRYPTION_KEYstatus must name an allowed primary or subkey; otherwise the plaintext is discarded. A ciphertext with an allowed visible recipient cannot silently decrypt through a different secret key. - GnuPG configuration is deterministic. The runner uses
--no-optionsand--pinentry-mode ask, so a hostilegpg.confcannot switch on loopback pinentry or change other security-relevant defaults. - Filesystem paths are checked without following symlinks. The config path is walked with
directory file descriptors and
O_NOFOLLOW, and the opened file is validated throughfstat, so validation and use cannot race or be redirected by a path swap. The keyring path is kept unresolved (GNUPGHOMEis not symlink-resolved);serve,doctorandinitvalidate its full ancestor chain and reject a symlinked keyring or ancestor. Ancestors must be owned by the user or root and must not be group/world writable without the sticky bit. Unknown keys are rejected. Existing audit files arefstated, non-regular/foreign-owned files are refused, and group/other access is tightened to0600. Withaudit_required = true, an audit failure fails the operation.initsecurely creates the config withO_EXCL|O_NOFOLLOWand refuses a symlinkedgpg-agent.confinstead of writing through or chmodding its target. Missing config, keyring and audit directories are created component-by-component relative to already validated directory descriptors, so a symlinked ancestor cannot redirect or create anything before rejection. The audit path uses the same directory-fd walk, so an unsafe ancestor cannot redirect the ledger. - Decryption routing is unambiguous.
symmetric_decryptrejects hybrid ciphertext that also carries public-key recipients.decryptrejects symmetric-only ciphertext and refuses to return plaintext unless GnuPG reports an actual allow-listed public key; it never falls back to the symmetric route. A custom policy that gates onlysymmetric_decrypttherefore cannot be bypassed throughdecrypt. For hybrid input, only GnuPG's recoverable symmetric-passphrase failure is tolerated, and only when a public key also reportedDECRYPTION_KEYandDECRYPTION_OKAY; genericERROR/FAILUREevents and unexplained nonzero exit codes are rejected. - Multi-signature handling is fail-closed. Detached signatures use a per-packet fallback on
GnuPG versions without
--proc-all-sigs. Cleartext, inline, and embedded signatures use--proc-all-sigswhen supported. On older GnuPG a bad-first message may stop enumeration at the first bad signature, but it is still rejected; all-good messages enumerate every signature. - Symmetric decryption is equally strict. It requires
DECRYPTION_OKAY, rejectsDECRYPTION_FAILED,BADMDC,ERRORandFAILURE, and treats unexplained nonzero exits as fatal. Withverify=false, a nonzero exit is tolerated only for an explicitly recognized embedded-signature outcome; unrelated GnuPG failures are never excused.
Verification result semantics
verify (and the embedded-signature result returned by decrypt) separates four ideas:
Decryption releases plaintext only when every embedded signature is policy_acceptable. A
mathematically valid but expired or revoked signature is never enough, and a bad signature cannot be
hidden by a later good one. The result includes a signatures list with one entry per NEWSIG
sequence.
What it does not guarantee
- Same-user isolation. Any process running as your OS user can read
~/.gnupg, connect to thegpg-agentsocket, or edit~/.config/gpg-mcp/config.toml. If the agent has shell access, the MCP policy is not an OS boundary. Use stronger isolation. - A malicious MCP client. In
approval = "client"mode, the client renders the approval dialog. A malicious client can fake it. Useapproval = "local"(zenity, independent of the client) or a separate user/hardware token. - Compromised gpg or gpg-agent. A compromised system binary can do anything. The binary checks reduce PATH hijacking, not supply-chain compromise.
- Semantic safety. The server can show what is being signed (hash and preview) but cannot judge whether the content is safe to sign. That judgement is the human's.
Threat table
Architecture
The server never opens private key files. It asks gpg to perform an operation; gpg-agent
unlocks the key only after pinentry succeeds.
Install
PyPI: gpg-mcp. MCP Registry: io.github.emiliano-go/gpg-mcp.
Pin the version in client config so a restart does not change the server build.
First run. If you did not install a CLI, prefix these with uvx [email protected]:
MCP client config, with no global install:
If you installed with uv tool or pipx:
Requirements: Python 3.11+, GnuPG 2.2+ (2.4 recommended), pinentry, and zenity when using
approval = "local". Linux and other Unix-like systems are supported; Windows is not.
The registry entry is searchable at
https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.emiliano-go/gpg-mcp.
Quick start (from source)
Requirements: Python 3.11+, GnuPG 2.2+ (2.4 recommended), a pinentry program.
init creates an isolated keyring at ~/.local/share/gpg-mcp/gnupg (mode 0700) and a config
at ~/.config/gpg-mcp/config.toml (mode 0600). It writes a conservative gpg-agent.conf:
ignore-cache-for-signing means every signature asks pinentry again. Remove it only if you
understand the trade-off.
Check the installation:
Add it to your MCP client (example for a JSON mcpServers map):
Then, from the agent, create a key with create_key (the passphrase is entered into pinentry) and
use the returned fingerprint with sign, encrypt or decrypt.
Client timeout: approval plus pinentry can take longer than a client's default MCP tool timeout. Set it to at least 120 seconds; 300 seconds is a comfortable default for key generation and passphrase changes. For opencode:
If the client times out while a dialog is open, cancel the dialog. The server does not auto-approve and will deny the operation.
The dedicated keyring starts empty.
gpg-mcpwill not copy or export your existing secret keys. To reuse an existing key, move it yourself out-of-band, or keep using~/.gnupgwith an explicitallowed_sign_keys/allowed_decrypt_keysallow-list.
Configuration
Config path: ~/.config/gpg-mcp/config.toml, or --config, or GPG_MCP_CONFIG.
Environment overrides: GPG_MCP_CONFIG, GPG_MCP_HOME, GPG_MCP_APPROVAL,
GPG_MCP_ENABLE_NETWORK, GPG_MCP_ENABLE_SYMMETRIC, GPG_MCP_AUDIT,
GPG_MCP_ALLOW_TEST_MODE, GPG_MCP_AUDIT_REQUIRED.
Tools
Read-only:
Crypto:
Key management:
Network (opt-in):
Deliberately absent
export_secret_key/ secret-key backup.run_gpg/ raw argv passthrough.- Any passphrase parameter.
--homedirfrom a tool.- Arbitrary keyserver URLs per call.
--edit-keyinteractive menu.--trust-modelfrom user input.
Approval modes
client (default)
The server asks the MCP client to show an approval form. The form includes the operation, full key fingerprints and identities, payload size, SHA-256 and a short preview. It is implemented as an MCP resolver, so it works with both legacy and current protocol versions.
If the client did not declare elicitation support (for example, some CLI clients), the server falls
back to the local zenity dialog. That is still a human gate and is independent of the MCP client.
If neither elicitation nor a safe local dialog is available, the call fails closed with an
approval_unavailable error; it never auto-approves.
Composite operations require approval if any constituent requires it. sign_and_encrypt inherits
both sign and encrypt; encrypt(..., sign_with=...) inherits sign as well. A custom
require_approval = ["sign"] therefore still gates encryption-with-signing, and
require_approval = ["encrypt"] still gates sign_and_encrypt.
Destructive operations require the typed fingerprint suffix whenever
destructive_confirmation = true (the default), even if the operation was removed from
require_approval. Setting it to false removes only the typed-confirmation requirement; approval
still applies if the operation remains in require_approval. approval = "none" bypasses both for
disposable test keyrings.
For revocation imports, the approval form shows the identity derived from the certificate itself, not merely the fingerprint passed as an argument. A certificate for a different key is rejected before approval is requested.
local
The server uses zenity (or fails closed when no display is available). This is independent of the
MCP client and is the better choice when you do not fully trust the client. Destructive operations
use a second zenity --entry prompt for the fingerprint suffix.
none
For disposable test keyrings only. serve refuses this mode unless allow_test_mode = true
(or GPG_MCP_ALLOW_TEST_MODE=1) and a dedicated GNUPGHOME are configured. The audit ledger
records decision: skipped. Never enable it for real keys.
Keyring isolation
gpg-mcp init uses a dedicated keyring. That gives you:
- a separate
gpg-agentcache from your everyday GnuPG use; - a policy boundary where an empty allow-list means “this keyring only”;
- a safe place for test keys and agent-specific keys.
The dedicated keyring is not a security boundary against the same OS user. For that, see below.
Stronger isolation
Separate OS user (software)
Run the signer as a different user that owns the keyring, and expose only a narrow Unix socket to the MCP server. The agent's user then cannot read the private keys or connect to the agent socket. This is the strongest software-only design and is a natural next step; the current release keeps the same-user model for simplicity.
Hardware token (recommended for high-value keys)
Put the private key on an OpenPGP smartcard or YubiKey. The key never exists on disk, and each
signature can require a PIN and a physical touch. gpg-mcp works unchanged: card_status reports
the card and its keys, and list_keys returns secret-key metadata. Key generation on the card is
done out-of-band with gpg --edit-card.
systemd hardening
If you run the server as a service, at minimum:
and run it as a dedicated user in the separate-user design.
Audit ledger
Every high-risk operation appends one JSON object to audit.jsonl:
The ledger contains no plaintext, no payload previews, no passphrases and no private key material.
Approval summaries are rendered for the human but never written to the ledger. The file is
append-only with mode 0600. On every write, an existing audit file is fstated: non-regular
files and files owned by another user are refused, and an existing file with group/other access is
tightened to 0600 with fchmod. gpg-mcp only tightens permissions on an audit directory it
creates itself; it never chmods an existing arbitrary parent such as /tmp.
With audit_required = true (the default), a high-risk operation fails if the audit entry cannot be
written; set audit_required = false only for disposable test keyrings. Records carry a phase:
approval/intent are written before execution, outcome after it. If an outcome append fails, the
error explicitly says the operation may already have completed; the operation is not retried. The
ledger is not tamper-proof against the same OS user.
Testing
The default suite uses pinentry-program /bin/false in its throwaway keyring, so any unexpected
prompt fails the test instead of blocking. It creates an ephemeral keyring and runs real gpg
operations. It asserts, among other things, that:
- no response contains
PRIVATE KEY BLOCK; - no command line contains a forbidden option or a passphrase;
- secret-key imports and secret-key revocation imports are rejected;
- the default policy requires approval and typed confirmation for
import_revocation,revoke_keyanddelete_key; - composite operations inherit constituent approval requirements;
- revocation dry-runs do not modify the real keyring, and certificates for another key are rejected;
- expired signatures, expired keys, revoked keys and missing public keys are never policy-acceptable;
- multiple embedded signatures fail closed in both good/bad orders;
fetch_keyis registered as a write operation and requires approval;- multiline/bidi UIDs are escaped in approval dialogs and validated before approval;
- an existing
0644audit file is tightened to0600, while symlinks and FIFOs are refused; - destructive confirmation remains required even when the operation is removed from
require_approval; - a revocation certificate cannot revoke through
import_public_key; - the actual
DECRYPTION_KEYmust be allow-listed; - status output over the configured limit fails closed;
- a hostile
gpg.confis ignored; - symmetric-only input is rejected by
decryptwhile hybrid input succeeds through an allow-listed public key; - a custom policy that gates only
symmetric_decryptcannot be bypassed throughdecrypt; - config ancestors are walked securely and non-sticky writable ancestors are rejected;
initrefuses a symlinkedgpg-agent.confand a dangling config symlink cannot create its target;- the audit path rejects non-sticky writable ancestors;
- multi-signature cleartext, inline, and embedded messages enumerate and aggregate correctly;
- hybrid decryption tolerates only the recoverable symmetric-passphrase failure pattern;
- symmetric decryption rejects unexplained nonzero exits and only excuses recognized signature failures when
verify=false; - config, keyring and audit directory creation cannot write through a symlinked ancestor;
- symlinked keyring paths and writable keyring ancestors are rejected at serve/doctor/init time;
- invalid secure-walk paths do not leak directory descriptors;
- multiple signatures are enumerated and fail closed both with and without
--proc-all-sigs; - hybrid symmetric/public-key ciphertext is rejected by
symmetric_decrypt; - audit short writes, intent/outcome phases, and outcome failures are handled;
- dangling config symlinks and unsafe config parents are rejected;
- multibyte UTF-8 exceeds the byte limit before a large allocation;
- unsafe config files, unknown config keys, and implicit
approval="none"are rejected; - unsafe audit files raise when
audit_required=true; - key-generation combinations are validated and partial imports are classified;
- the tool registry matches the read/write annotations for every exposed tool;
- decryption refuses ciphertext addressed to another key and mixed hidden/visible recipients;
- the audit ledger contains no payload preview and does not change an existing directory's mode;
- unsafe
zenity/binary permissions are rejected; - option-injection attempts fail validation.
CI runs ruff and pytest on Python 3.11, 3.12 and 3.13 with GnuPG installed (.github/workflows/ci.yml).
main is protected and requires all three CI jobs; direct pushes are rejected.
Limitations
- Text and base64 inputs are capped; large files should be handled out-of-band.
- Keyserver search output is human-readable text, not a structured list.
- Revocation uses the certificate generated at key creation time. If it is missing, generate one
out-of-band with
gpg --gen-revoke. The target key must already be present in the configured keyring; revocation certificates for unknown keys are rejected.import_revocationhandles full primary-key revocation, not selective subkey or UID revocation. - Smartcard key generation and PIN management are intentionally out of scope.
approval = "client"depends on the client rendering elicitation forms honestly. If the client cannot elicit, approval falls back to a validated localzenitydialog.- Linux/Unix only: the implementation relies on Unix permissions,
os.getuid, POSIX process groups, andzenity; Windows is not supported. generate_revocationreturns a revocation-capable certificate to the caller. Treat the result as sensitive and store it offline. The approval prompt explains the consequence.- On GnuPG versions without
--proc-all-sigs, cleartext, inline, and embedded signature enumeration may stop at the first bad signature; the result is still rejected. Detached signatures use the per-packet fallback.
License
MIT. See LICENSE.
來源:README.md,提交 406b686
工具
0版本歷史
1- v0.1.0最新Oct 26, 2026
評分
已滿足適用檢查項的 50%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
- 近 30 天內可連通25 分
請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。
- 已宣告認證方式10 分
請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。
- 已列出工具15 分
請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。
- 描述必需15 分
- 圖示10 分
- 文件15 分
- 授權條款10 分