I added 1Password support to an MCP secrets runner and the first thing I had to do was turn one of its security features off. Here is why that was the right call.
What op run does
1Password's CLI resolves op:// references and hands the values to a child process:
op run --env-file=./mcp.env -- npx -y @stripe/mcp@latest
It also does something thoughtful on top. It watches the child's output and rewrites anything matching a resolved secret to <concealed by 1Password>. If your program accidentally logs a token, or you are screen sharing, the secret does not leave the machine.
For a normal CLI this is a good default. I would keep it on.
What an MCP server is
An MCP server on the stdio transport speaks JSON-RPC over stdout. Not log lines. A framed protocol stream, where the client parses every byte:
{"jsonrpc":"2.0","id":1,"result":{"tools":[...]}}
stdout is not a place for human-readable output. It is a wire.
The collision
Put those two together. Masking rewrites bytes in a stream that a parser is reading with exact expectations. If a secret value ever appears inside a frame, the frame changes length, and the parse fails.
What makes this genuinely nasty is where it fails. The MCP client reports a protocol error. Nothing in that error mentions 1Password, masking, or secrets. You would go and read your server's serialisation code, which is fine, for a long time.
So the provider passes --no-masking by default, and the README says why in as many words. There is an opt-in flag to turn masking back on for servers that do not use stdio.
The general shape
What I find worth writing down is that nothing here is a bug. Not in 1Password, not in MCP, not in my code.
A security feature assumes it may rewrite a child's output. A wire protocol assumes it owns stdout exclusively. Both assumptions are reasonable in isolation. The failure lives in the space between them, and it is silent, because neither component is in a position to notice the other exists.
Every integration you write has a few of these. They do not show up in either project's issue tracker, because from each side nothing is wrong. They show up in yours.
The practical takeaway: when you wrap a process that speaks a protocol on stdout, audit everything in the chain for the assumption that stdout is for humans. Log prefixes, colour codes, progress bars, and yes, secret masking.
Top comments (0)