TRMNL is an awesome open-source ePaper display that lets users show almost anything they want. Launched on Kickstarter, it’s now got thousands of plugins and users. I’ve published a few plugins, including a realtime TfL bus stop arrival time board.
A few weeks ago, TRMNL added an MCP server. This lets you use a local coding agent to do most of the grunt work.
Install the MCP server through .mcp.json
Using Claude Code, i created a simple .mcp.json file which references the API key by environment variable. Place this in the root of your repo. As it contains no secrets, this is safe to commit:
{
"mcpServers": {
"trmnl": {
"type": "http",
"url": "https://trmnl.com/mcp?api_key=${TRMNL_MCP_API_KEY}"
}
}
}I previously wrote about how to inject the environment variable at launch time so that Claude can access the MCP server.
Each plugin gets its own MCP API key
The TRMNL MCP server is designed so that each plugin gets its own MCP API key, which i store in 1Password. But i store my plugins in a single “monorepo” - so my standard approach of just injecting at runtime wouldnt work because a single .env.tpl at the root of the repo can only hold a single MCP API key, so i would have to manually edit that file every time i wanted to switch plugin.
The best approach seemed to be to store each MCP API key in a field in a single 1Password entry, and dynamically load the correct MCP API key for each claude session, but i was quickly forgetting the exact magic incantation (command).
Store a .env.tpl in each plugin folder, and reference that
Each plugin directory gets its own .env.tpl which maps variable names to op:// references, but as there are no secrets it is safe to commit it to git. Each file includes that specific plugin's MCP key under the same variable name, and i can add whatever other plugin-specific secrets i need. For example:
# national_rail_departures/.env.tpl
TRMNL_MCP_API_KEY=op://TRMNL/trmnl/mcp_api_key_national_rail_departures
RAILDATA_API_KEY=op://TRMNL/raildata/1010-live-departure-board_consumer_keyand
# simple_tube_status/.env.tpl
TRMNL_MCP_API_KEY=op://TRMNL/trmnl/mcp_api_key_simple_tfl_status.mcp.json at the repo root reads ${TRMNL_MCP_API_KEY} from the environment, and that is injected through whichever template we load.
Now we can launch Claude with a single command:
op run --no-masking --env-file="<plugin_folder>/.env.tpl" -- claudeCreate a shortcut which is easy to remember
That’ll work, but like i said before, it’s hard to remember, so I added a simple shell script which takes the folder name, loads the .env.tpl file from that folder, and injects the resolved secrets to the Claude process as environment variables. The script is saved as bin/op_claude in the project folder.
#!/bin/sh
# Usage: op_claude <plugin_dir> [claude args...]
cd "$(dirname "$0")/.." || exit 1
plugin="$1"
if [ ! -f "$plugin/.env.tpl" ]; then
echo "usage: op_claude <plugin_dir> [claude args...] (no $plugin/.env.tpl found)" >&2
exit 2
fi
shift
exec op run --no-masking --env-file="$plugin/.env.tpl" -- claude "$@"I also added a single line to my .envrc to add the bin folder to the current path, but only when we’re in the repo folder:
PATH_add binLaunch with a simple command
You can now just type op_claude to launch e.g.
claudelaunches Claude like normal, no API keys, no secrets. The MCP server fails to load.op_claudetells you how to use the toolop_claude national_rail_departureslaunches Claude and injects.env.tplfile from thenational_rail_departuresfolderop_claude simple_tube_status --resume <session-id>launches Claude, injectssimple_tube_status/.env.tpl, and resumes session<session-id>.
Each session sees exactly the correct MCP API key with no manual editing required.
No comments yet