yunohost-mcp
Secure MCP server for YunoHost: Nostr-authenticated, policy-controlled, auditable administration, diagnostics, and _ynh package development for AI clients (Codex, Claude, OpenCode, ChatGPT-compatible MCP clients).
See PLAN.md for the full design and phased roadmap, and PHASE0_INVESTIGATION.md for how it maps onto YunoHost's actual Python API.
Running the server
yunohost-mcp --transport stdio # local development, fully trusted (no NIP-98 handshake)
yunohost-mcp --transport http --host 127.0.0.1 --port 8765 # NIP-98-authenticated, remote-capable
By default fake_yunohost is off (real mode) — set YUNOHOST_MCP_FAKE_YUNOHOST=true to run against canned data on a machine without YunoHost installed. See identity.example.toml for identity.toml's shape (pubkey → role mapping; required before any HTTP request can do anything).
Installing the client tools
Once published, install the Python package with:
python3 -m pip install yunohost-mcp-connect
This installs yunohost-mcp, yunohost-mcp-connect, yunohost-mcp-delegate,
and yunohost-mcp-approve. For an isolated command-line installation, use:
uv tool install yunohost-mcp-connect
Connecting a client: yunohost-mcp-connect
Mainstream MCP clients (Claude Desktop, a plain Codex install, etc.) have no way to sign a NIP-98 Authorization header — that's specific to this server. yunohost-mcp-connect bridges the gap: a small local process that speaks plain MCP over stdio to your actual client, and forwards every request to the remote --transport http server, signed with your own Nostr key.
yunohost-mcp-connect --remote-url https://your-yunohost-domain/mcp --key-file ~/.config/yunohost-mcp/key
--key-file(or$YUNOHOST_MCP_CLIENT_KEY_FILE) points at a file holding a hex ornsec1...private key — preferred over--key/$YUNOHOST_MCP_CLIENT_KEY, which put the key in argv/environment where other processes on the same machine can read it.--generate-key PATHwrites a fresh private key toPATH(0600; refuses to overwrite an existing file), prints its npub, and exits without connecting anywhere — the way to get a--key-filein the first place. Use a distinctPATHper client; see "Give each client its own key file" below.--delegation-file(or$YUNOHOST_MCP_CLIENT_DELEGATION_FILE) presents a delegation event (PLAN.md Phase 11) alongside your own signature, for a disposable agent identity an owner granted a subset of their access to.- Point your MCP client's config at this command (not the server directly) —
tools/list,tools/call,resources/list, andresources/readare all forwarded verbatim; every other MCP feature and all authorization/policy/audit still happens exactly as it would if you'd signed the request yourself, because you did.
Connecting Claude Desktop or Codex
Both point at yunohost-mcp-connect, not at the server directly — the bridge is what signs each request with your Nostr key. Use the full path to yunohost-mcp-connect in whatever environment you installed yunohost-mcp into (e.g. ~/.local/pipx/venvs/yunohost-mcp/bin/yunohost-mcp-connect, or a venv's bin/ directory — which yunohost-mcp-connect after activating it will tell you).
Give each client its own key file. YUNOHOST_MCP_CLIENT_KEY_FILE is the identity — whichever key signs a request determines its role and scopes on the server, nothing else. Point two different clients (or two different config files for the same client — a project-local .codex/config.toml shadows ~/.codex/config.toml) at the same key file and the second one silently authenticates as the first, with its exact permissions - no error, nothing to notice. Generate a fresh key per client rather than copying one that already works:
yunohost-mcp-connect --generate-key ~/.config/yunohost-mcp/claude-desktop.key
yunohost-mcp-connect --generate-key ~/.config/yunohost-mcp/codex.key
Each prints the new key's npub - grant it whatever role is appropriate for that specific client (see "Granting a disposable agent identity access" below, or identity.toml directly), not the role you already gave a different one. --generate-key refuses to overwrite a file that already exists.
Claude Desktop (claude_desktop_config.json — Settings → Developer → Edit Config):
{
"mcpServers": {
"yunohost-mcp": {
"command": "/full/path/to/yunohost-mcp-connect",
"env": {
"YUNOHOST_MCP_CLIENT_REMOTE_URL": "https://your-yunohost-domain/mcp",
"YUNOHOST_MCP_CLIENT_KEY_FILE": "/home/you/.config/yunohost-mcp/claude-desktop.key"
}
}
}
}
Codex CLI (~/.codex/config.toml):
[mcp_servers.yunohost-mcp]
command = "/full/path/to/yunohost-mcp-connect"
[mcp_servers.yunohost-mcp.env]
YUNOHOST_MCP_CLIENT_REMOTE_URL = "https://your-yunohost-domain/mcp"
YUNOHOST_MCP_CLIENT_KEY_FILE = "/home/you/.config/yunohost-mcp/codex.key"
Run yunohost-mcp-connect --key-file <path> once per new key to see its npub (yunohost-mcp-connect: signing as npub1...), then grant that npub whatever role is actually appropriate for that client in identity.toml - not the role you already gave a different one.
For a delegated (disposable) identity instead of your own key, add YUNOHOST_MCP_CLIENT_DELEGATION_FILE pointing at the file yunohost-mcp-delegate produced (see below). Restart the client after editing its config — both read this file once, at startup.
Granting a disposable agent identity access: yunohost-mcp-delegate
An identity.toml entry grants access to one specific pubkey, permanently (until edited). A delegation (PLAN.md Phase 11) is the other way to grant access: an owner signs a short-lived, scoped grant to an agent's own disposable key, without ever adding that key to identity.toml or handing over any private key. yunohost-mcp-delegate is what an owner runs to create one:
yunohost-mcp-delegate --key-file ~/.config/yunohost-mcp/key \
--delegate npub1... \
--remote-url https://your-yunohost-domain/mcp \
--role readonly --ttl 24h \
--out agent-delegation.json
--delegateis the agent's own pubkey (it must sign its own NIP-98 requests as always — a delegation never replaces that, it only adds standing).--remote-urlfetches the server's pubkey and this owner's own current scopes automatically — no need to type the server's pubkey by hand, and an over-broad--scope/--rolerequest is flagged (the server can never grant more than the delegator's own current scopes; seeauth/delegation.py). Pass--serverinstead if you'd rather supply the server's pubkey directly.--scope/--role(repeatable, combinable) choose what to grant;--ttl(e.g.24h,7d) how long — the server rejects anything over 30 days.- The output is a signed delegation event: a bearer credential once issued. Hand the file to the agent to use with
yunohost-mcp-connect --delegation-file agent-delegation.json, over a channel you trust (the same as you'd hand over an API key). - To take a delegation back before it expires, add its
id(printed after signing) torevoked_delegations.toml— this is independent of, and finer-grained than, removing the delegator's ownidentity.tomlentry (which revokes every delegation they've ever issued).
Approving high-risk operations: yunohost-mcp-approve
Some operations (system_upgrade, backup_restore, system_migrate, user_delete, permission changes, firewall changes) require owner co-signature on top of the requester's own confirmation (PLAN.md Phase 13, solo profile - see docs/owner-approval-plan.md in the packaging repo for the full design). The requester's call pauses with a confirmation_id; the configured owner reviews and approves it with yunohost-mcp-approve, signing through their own NIP-46 remote signer app (Amber, nsec.app, ...) - their private key never touches this server or the requesting agent's machine.
One-time setup, on whatever device the owner keeps their signer app on:
yunohost-mcp-approve pair
Prints a nostrconnect:// URI (and a QR code, if the optional qrcode package is installed - pip install 'yunohost-mcp-connect[approve-qr]') to open in the signer app. This persists a reconnectable session locally so later approvals don't need to re-pair.
To review and approve a specific pending operation:
yunohost-mcp-approve approve --server https://your-yunohost-domain/mcp --confirmation-id confirm-...
This fetches the authoritative pending-operation record from the server (never trusts a locally-supplied plan), displays the exact tool, arguments, and operation_hash, and requires typing yes before submitting the signed approval. Once approved, the original requester can retry its call.
Development
uv sync --group dev
uv run pytest -q
Creating a release tag
Maintainers can run the GitHub Actions Tag release workflow manually from
the branch or commit to release, providing a version without the v prefix
(for example, 0.1.1). It validates the version, refuses to overwrite an
existing tag, runs the full CI checks on Python 3.11 and 3.12, and only then
pushes an annotated v<version> tag.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file yunohost_mcp_connect-0.7.9-py3-none-any.whl.
File metadata
- Download URL: yunohost_mcp_connect-0.7.9-py3-none-any.whl
- Upload date:
- Size: 123.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3f5e5df9b9b2045a7deefdabce8581683e2a8e6868a3df269bb1e8b8f864351c
|
|
| MD5 |
1f0c118a75842fd3cf9d8b02dc687993
|
|
| BLAKE2b-256 |
d36465c24a02781b83ae700d8fa94aa0ff3c1fbb0fad63ea5ebf93a7b630fd55
|
Provenance
The following attestation bundles were made for yunohost_mcp_connect-0.7.9-py3-none-any.whl:
Publisher:
publish-pypi.yml on imattau/yunohost-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
yunohost_mcp_connect-0.7.9-py3-none-any.whl -
Subject digest:
3f5e5df9b9b2045a7deefdabce8581683e2a8e6868a3df269bb1e8b8f864351c - Sigstore transparency entry: 2717109030
- Sigstore integration time:
-
Permalink:
imattau/yunohost-mcp@ae6704a947d749585c786a8296e56fa0759f8d2f -
Branch / Tag:
refs/tags/v0.7.9 - Owner: https://github.com/imattau
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@ae6704a947d749585c786a8296e56fa0759f8d2f -
Trigger Event:
push
-
Statement type: