coder: let the omp harness drive workspaces over Coders MCP server
Coder ships `coder exp mcp server` and omp is an MCP client, so the two compose with no custom code. The agent stays on the host where its credentials already live and reaches into the workspace, rather than shipping secrets into a container. - scripts/coder-mcp.sh wraps the server, passing a dedicated `omp-mcp` token via `docker exec --env-file` (mode 600 temp file, shredded by an EXIT trap) so the token never appears in argv or ps - allow-list exposes 26 of 32 tools; template mutation is excluded because pushing a template is the one real path from a Coder principal to the host docker socket - scripts/coder-token-rotate.sh handles the 168h token lifetime cap: mint, verify against the API, then revoke the predecessors - .omp/mcp.json registers it project-scoped so 26 tools do not load into every unrelated omp session The docker exec must stay in the foreground: backgrounding it makes bash redirect stdin from /dev/null with job control disabled, and the MCP transport dies on EOF with "MCP tool load failed ... Transport closed". Verified: fresh omp session in this directory answered from the tools - coder_get_authenticated_user returned loca, workspace ws1 running.
This commit is contained in:
50
scripts/coder-token-rotate.sh
Executable file
50
scripts/coder-token-rotate.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# Rotate the dedicated Coder token used by the omp MCP integration.
|
||||
#
|
||||
# Coder caps token lifetime at 168h (7d) on this deployment, so this must be run
|
||||
# weekly or the agent's Coder tools start returning 401.
|
||||
#
|
||||
# Mints a fresh `omp-mcp` token, writes it to .mcp-token (mode 600), then
|
||||
# revokes every older token of the same name. The value is never printed.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="/home/loca/dev/coder"
|
||||
CRED="$ROOT/.admin-credentials"
|
||||
TOKEN_FILE="$ROOT/.mcp-token"
|
||||
CONTAINER="coder"
|
||||
NAME="omp-mcp"
|
||||
LIFETIME="168h"
|
||||
|
||||
[[ -r "$CRED" ]] || { echo "missing credentials file: $CRED" >&2; exit 1; }
|
||||
|
||||
url="$(sed -n 's/^url=//p' "$CRED")"
|
||||
session="$(sed -n 's/^session_token=//p' "$CRED")"
|
||||
[[ -n "$url" && -n "$session" ]] || { echo "url= or session_token= missing in $CRED" >&2; exit 1; }
|
||||
|
||||
ENVF="$(mktemp)"
|
||||
chmod 600 "$ENVF"
|
||||
trap 'rm -f "$ENVF"' EXIT
|
||||
printf 'CODER_URL=%s\nCODER_SESSION_TOKEN=%s\n' "$url" "$session" >"$ENVF"
|
||||
|
||||
coder() { sudo -n docker exec -i --env-file "$ENVF" "$CONTAINER" /opt/coder "$@"; }
|
||||
|
||||
# Existing token ids for this name, captured before minting the replacement.
|
||||
old_ids="$(coder tokens list | awk -v n="$NAME" 'NR>1 && $2==n {print $1}')"
|
||||
|
||||
new="$(coder tokens create --name "$NAME" --lifetime "$LIFETIME" | tail -n1 | tr -d '[:space:]')"
|
||||
[[ -n "$new" ]] || { echo "token creation produced no value" >&2; exit 1; }
|
||||
|
||||
umask 077
|
||||
printf '%s\n' "$new" >"$TOKEN_FILE"
|
||||
chmod 600 "$TOKEN_FILE"
|
||||
|
||||
# Verify before revoking the old ones, so a bad mint cannot lock us out.
|
||||
code="$(curl -s -o /dev/null -w '%{http_code}' -H "Coder-Session-Token: $new" "$url/api/v2/users/me")"
|
||||
[[ "$code" == "200" ]] || { echo "new token failed verification (http $code); old tokens left intact" >&2; exit 1; }
|
||||
|
||||
for id in $old_ids; do
|
||||
coder tokens remove "$id" >/dev/null && echo "revoked old token $id"
|
||||
done
|
||||
|
||||
echo "rotated $NAME; expires in $LIFETIME"
|
||||
coder tokens list
|
||||
Reference in New Issue
Block a user