Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Using hush from an agent needed a clone and docs/MCP.md. It now needs one
command, and the instructions are served by the deployment itself.
`go install github.com/orchard9/hush/cmd/hush-mcp@latest` is the whole
install: cmd/hush-mcp imports only the standard library, so module graph
pruning never reaches the private go-chassis dependency cmd/hushd needs.
Verified against an empty module cache and the public proxy, then create ->
reveal end to end against production with the resulting binary.
The page carries the per-client configuration for Claude Code, Codex CLI,
Gemini CLI, VS Code, Claude Desktop, Cursor and omp. Each command was run
against the installed client rather than copied from documentation, which is
how the differences on it are there at all: VS Code's wrapper key is
`servers`, not `mcpServers`; gemini defaults to project scope, not user;
Claude Code rejects `--env` immediately before the server name.
The shared browser crypto moves from base.html into templates/crypto.html,
which the two pages that encrypt parse and this one does not. An empty
`{{define}}` cannot replace a non-empty one — text/template reads an empty
body as no definition — so the shell holds the call and the partial holds the
code, and the docs page ships no script at all.
Three things this exposed, fixed here:
- The public Ingress enumerates paths, so a handler without one 404s at the
edge while working in `make dev`. The Ingress is now its own manifest:
hush.yaml pins a `:bootstrap` image that does not exist, so re-applying it
to publish a path would roll the workload onto an unpullable image.
`make deploy-ingress` applies the route alone.
- release.sh guarded HEAD against `@{upstream}`, which is the GitHub mirror
here, while Kaniko clones Gitea. A commit pushed to one and not the other
would have built the previous commit silently. It now fetches and compares
the branch that actually gets built.
- smoke.sh checks that /mcp serves the install command, so a stale rollout or
an unexecutable template fails the release instead of being found later.
Confirmed it fails: against production before this deploy it reported 404.
205 lines
9.2 KiB
HTML
205 lines
9.2 KiB
HTML
{{define "styles"}}
|
|
<style nonce="{{.Nonce}}">
|
|
/* Doc-page-only CSS. It lives here rather than in base.html so the create and
|
|
reveal pages ship no bytes for prose they do not contain — and so these
|
|
selectors can name bare elements without touching those pages. */
|
|
main{
|
|
max-width:720px;
|
|
/* body centres its one item vertically; a page taller than the viewport has
|
|
to start at the top instead. */
|
|
align-self:flex-start;margin:8px 0 28px;
|
|
}
|
|
.card{padding:30px 30px 26px}
|
|
h2{
|
|
font-size:14px;font-weight:600;letter-spacing:-.01em;color:var(--fg);
|
|
margin:28px 0 10px;padding-top:22px;border-top:1px solid var(--line);
|
|
}
|
|
h3{font-size:13px;font-weight:600;margin:20px 0 7px;color:var(--fg)}
|
|
p{color:var(--dim);font-size:13.5px;margin:0 0 11px}
|
|
ul{margin:0 0 12px;padding-left:17px;color:var(--dim);font-size:13.5px}
|
|
li{margin:0 0 7px}
|
|
pre{
|
|
background:var(--inset);border:1px solid var(--line);border-radius:10px;
|
|
color:var(--fg);font:12.5px/1.65 var(--mono);padding:12px 13px;margin:0 0 12px;
|
|
overflow-x:auto;user-select:all;
|
|
}
|
|
code{font:12.5px var(--mono);color:var(--fg)}
|
|
table{width:100%;border-collapse:collapse;font-size:13px;margin:0 0 14px}
|
|
th{
|
|
color:var(--faint);font:500 11px/1.5 -apple-system,system-ui,sans-serif;
|
|
text-transform:uppercase;letter-spacing:.07em;text-align:left;
|
|
padding:0 12px 7px 0;border-bottom:1px solid var(--line);
|
|
}
|
|
td{
|
|
color:var(--dim);padding:8px 12px 8px 0;border-bottom:1px solid var(--line);
|
|
vertical-align:top;
|
|
}
|
|
td:first-child{color:var(--fg);font:12.5px var(--mono);white-space:nowrap}
|
|
.card a{color:var(--accent);text-decoration:none;border-bottom:1px solid #8ce0b840}
|
|
.card a:hover{border-bottom-color:var(--accent)}
|
|
.destructive{color:var(--warn)}
|
|
</style>
|
|
{{end}}
|
|
|
|
{{define "content"}}
|
|
<h1>hush<span>.</span> from an agent</h1>
|
|
<p class="lede">Two MCP tools. The encryption happens on your machine, so an
|
|
agent using hush gets the same guarantee a browser does.</p>
|
|
|
|
<table>
|
|
<tr><th>Tool</th><th>What it does</th></tr>
|
|
<tr><td>hush_create</td><td>Encrypts a secret locally, stores the ciphertext, returns a link that works exactly once</td></tr>
|
|
<tr><td>hush_reveal</td><td>Opens a link and destroys it</td></tr>
|
|
</table>
|
|
|
|
<p>It is a local binary rather than an endpoint on this server for one reason: if
|
|
the server did the encrypting, the server could read every secret an agent
|
|
created, and hush's claim would hold for browser users while quietly not
|
|
holding for you. <code>hush-mcp</code> is a peer of the browser — it mints the
|
|
AES-256 key, encrypts, posts only ciphertext, and assembles the
|
|
<code>#fragment</code> link itself.</p>
|
|
|
|
<h2>1. Install it</h2>
|
|
<pre>go install github.com/orchard9/hush/cmd/hush-mcp@latest</pre>
|
|
<p>Needs Go 1.26 or newer, and nothing else: the binary imports only the
|
|
standard library, so there is no dependency to resolve and no service to run.</p>
|
|
|
|
<p>Most clients do not expand <code>~</code>, so get the absolute path once and
|
|
paste that everywhere below:</p>
|
|
<pre>echo "$(go env GOPATH)/bin/hush-mcp"</pre>
|
|
|
|
<h2>2. Register it with your client</h2>
|
|
<p>Every client here launches the same binary over stdio. The JSON shape is the
|
|
portable part; only VS Code spells the wrapper key differently.</p>
|
|
|
|
<h3>Claude Code</h3>
|
|
<pre>claude mcp add hush -e HUSH_BASE_URL=https://hush.threesix.ai \
|
|
-- /Users/you/go/bin/hush-mcp</pre>
|
|
<p>The <code>--</code> is load-bearing: everything after it is the command to
|
|
launch, so Claude Code stops reading those arguments as its own. Add
|
|
<code>-s user</code> to get the server in every project rather than this one.
|
|
<code>claude mcp list</code> then prints
|
|
<code>hush: … ✔ Connected</code>.</p>
|
|
|
|
<h3>Codex CLI</h3>
|
|
<pre>codex mcp add hush --env HUSH_BASE_URL=https://hush.threesix.ai \
|
|
-- /Users/you/go/bin/hush-mcp</pre>
|
|
<p>That writes <code>~/.codex/config.toml</code>. The same thing by hand:</p>
|
|
<pre>[mcp_servers.hush]
|
|
command = "/Users/you/go/bin/hush-mcp"
|
|
env = { "HUSH_BASE_URL" = "https://hush.threesix.ai" }</pre>
|
|
<p>Confirm with <code>codex mcp get hush</code>, or <code>/mcp</code> in a
|
|
session.</p>
|
|
|
|
<h3>Gemini CLI</h3>
|
|
<pre>gemini mcp add hush /Users/you/go/bin/hush-mcp \
|
|
-e HUSH_BASE_URL=https://hush.threesix.ai -s user</pre>
|
|
<p>Without <code>-s user</code> the entry lands in the current project's
|
|
<code>.gemini/settings.json</code> instead of
|
|
<code>~/.gemini/settings.json</code>. Confirm with <code>/mcp</code> in a
|
|
session.</p>
|
|
|
|
<h3>VS Code</h3>
|
|
<pre>code --add-mcp '{"name":"hush","type":"stdio","command":"/Users/you/go/bin/hush-mcp","env":{"HUSH_BASE_URL":"https://hush.threesix.ai"}}'</pre>
|
|
<p>By hand the file is <code>.vscode/mcp.json</code> for one workspace, or the
|
|
user-level <code>mcp.json</code> that <strong>MCP: Open User
|
|
Configuration</strong> opens — and its wrapper key is <code>servers</code>,
|
|
not <code>mcpServers</code>:</p>
|
|
<pre>{
|
|
"servers": {
|
|
"hush": {
|
|
"type": "stdio",
|
|
"command": "/Users/you/go/bin/hush-mcp",
|
|
"env": { "HUSH_BASE_URL": "https://hush.threesix.ai" }
|
|
}
|
|
}
|
|
}</pre>
|
|
|
|
<h3>Claude Desktop, Cursor, omp, anything else</h3>
|
|
<p>One block, in that client's config file:</p>
|
|
<pre>{
|
|
"mcpServers": {
|
|
"hush": {
|
|
"type": "stdio",
|
|
"command": "/Users/you/go/bin/hush-mcp",
|
|
"env": { "HUSH_BASE_URL": "https://hush.threesix.ai" }
|
|
}
|
|
}
|
|
}</pre>
|
|
<table>
|
|
<tr><th>Client</th><th>File</th></tr>
|
|
<tr><td>Claude Desktop</td><td>macOS <code>~/Library/Application Support/Claude/claude_desktop_config.json</code>, Windows <code>%APPDATA%\Claude\claude_desktop_config.json</code> — then quit the app completely and reopen it</td></tr>
|
|
<tr><td>Cursor</td><td><code>~/.cursor/mcp.json</code> for every project, <code>.cursor/mcp.json</code> for one</td></tr>
|
|
<tr><td>omp</td><td><code>~/.omp/agent/mcp.json</code></td></tr>
|
|
</table>
|
|
<p>From a clone of the repo, <code>make mcp</code> does the omp case for you: it
|
|
builds the binary, proves the handshake before wiring anything, then rewrites
|
|
only hush's entry — backing the file up and leaving every other server
|
|
alone.</p>
|
|
|
|
<h2>3. Prove it before you trust it</h2>
|
|
<p>A misconfigured stdio server reaches you as an opaque “server disconnected”.
|
|
Run the handshake yourself instead, where the error is legible:</p>
|
|
<pre>printf '%s\n' \
|
|
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{}}}' \
|
|
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
|
|
| /Users/you/go/bin/hush-mcp</pre>
|
|
<p>Two JSON lines come back: the first names the server <code>hush</code>, the
|
|
second lists <code>hush_create</code> and <code>hush_reveal</code>. That is
|
|
the same binary, launched the same way, that your client will run.</p>
|
|
|
|
<h2>4. Use it</h2>
|
|
<p>Ask in words; the agent picks the tool.</p>
|
|
<ul>
|
|
<li>“Put this in a hush link so I can send it: <em><the credential></em>”</li>
|
|
<li>“Open this hush link: <em>https://hush.threesix.ai/s/…#…</em>”</li>
|
|
</ul>
|
|
<p>Three behaviours worth knowing before an agent calls either tool:</p>
|
|
<ul>
|
|
<li><span class="destructive">hush_reveal destroys the link.</span> After it
|
|
returns, the intended recipient cannot open it. An agent that reveals a link
|
|
“just to check” has burned it.</li>
|
|
<li><strong>The link is shown once.</strong> hush cannot rebuild it, because
|
|
the key it carries was never sent to the server.</li>
|
|
<li><strong>The <code>#fragment</code> is the key.</strong> Chat clients and
|
|
mail rewriters truncate fragments, and a link without one carries no key.
|
|
The tool says exactly that, without touching the secret.</li>
|
|
</ul>
|
|
|
|
<h2>Configuration</h2>
|
|
<table>
|
|
<tr><th>Variable</th><th>Meaning</th></tr>
|
|
<tr><td>HUSH_BASE_URL</td><td>Which deployment <code>hush_create</code> posts to. Defaults to <code>https://hush.threesix.ai</code></td></tr>
|
|
<tr><td>HUSH_CREATE_TOKEN</td><td>Only for a deployment that has closed anonymous create. Unset is the normal case</td></tr>
|
|
</table>
|
|
<p><code>hush_reveal</code> ignores both and reveals against the link's own
|
|
origin. A link minted by another hush deployment would be meaningless here,
|
|
and reporting it <em>gone</em> would be a lie about a secret nobody had
|
|
touched.</p>
|
|
|
|
<h2>When a client will not connect</h2>
|
|
<ul>
|
|
<li>Use the absolute path: most clients do not expand <code>~</code>.</li>
|
|
<li>Restart the client. Claude Desktop needs a full quit, not a window close.</li>
|
|
<li>Check the wrapper key — <code>servers</code> in VS Code,
|
|
<code>mcpServers</code> everywhere else.</li>
|
|
<li>Ask the client: <code>claude mcp list</code>,
|
|
<code>codex mcp get hush</code>, or <code>/mcp</code> in a session. VS Code
|
|
logs to <strong>Output → MCP</strong>, Claude Desktop to
|
|
<code>~/Library/Logs/Claude/mcp*.log</code>.</li>
|
|
<li>A <em>gone</em> error is not a connection fault: that link was already
|
|
opened, expired, or never existed. If you did not open it, assume someone
|
|
else did and rotate the secret.</li>
|
|
</ul>
|
|
|
|
<p>Source: <a href="https://github.com/orchard9/hush">github.com/orchard9/hush</a>.</p>
|
|
{{end}}
|
|
|
|
{{define "nav"}} · <a href="/">create a secret</a>{{end}}
|
|
|
|
{{/* This page runs no script. It does not parse templates/crypto.html, and it
|
|
defines the shell's two script hooks as nothing, so what is served here is
|
|
prose and only prose — no code on this page can reach a key. */}}
|
|
{{define "crypto"}}{{end}}
|
|
{{define "script"}}{{end}}
|