Using multiple TRMNL MCP API keys in a single plugin repo

mnbf9rca3 minHow-to

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_key

and

# 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" -- claude

Create 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 bin

Launch with a simple command

You can now just type op_claude to launch e.g.

  • claude launches Claude like normal, no API keys, no secrets. The MCP server fails to load.
  • op_claude tells you how to use the tool
  • op_claude national_rail_departures launches Claude and injects .env.tpl file from the national_rail_departures folder
  • op_claude simple_tube_status --resume <session-id> launches Claude, injects simple_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