#!/usr/bin/env bash # Coder MCP server, wrapped for the omp harness. # # Speaks MCP over stdio, so it is spawned as a child process by an MCP client # (omp registers it in .omp/mcp.json). It gives the agent Coder's own tool set: # workspace lifecycle, in-workspace bash/file operations, task management and # read-only template inspection. # # Credential handling: the token is written to a mode-600 temp file and handed # to `docker exec --env-file`, never passed as an argument. That keeps it out of # argv, out of `ps`, and out of any agent transcript. The file is removed when # this script exits. # # The token is a dedicated, revocable Coder token named `omp-mcp`, not the # operator session token. Rotate with: # scripts/coder-token-rotate.sh # List/revoke with: # sudo -n docker exec coder /opt/coder tokens list # sudo -n docker exec coder /opt/coder tokens remove set -euo pipefail TOKEN_FILE="/home/loca/dev/coder/.mcp-token" CRED="/home/loca/dev/coder/.admin-credentials" CONTAINER="coder" [[ -r "$TOKEN_FILE" ]] || { echo "missing token file: $TOKEN_FILE" >&2; exit 1; } [[ -r "$CRED" ]] || { echo "missing credentials file: $CRED" >&2; exit 1; } url="$(sed -n 's/^url=//p' "$CRED")" [[ -n "$url" ]] || { echo "no url= line in $CRED" >&2; exit 1; } ENVF="$(mktemp)" chmod 600 "$ENVF" trap 'rm -f "$ENVF"' EXIT { printf 'CODER_URL=%s\n' "$url" printf 'CODER_SESSION_TOKEN=%s\n' "$(tr -d '[:space:]' <"$TOKEN_FILE")" } >"$ENVF" # Tool allow-list. Template mutation is deliberately excluded: pushing a # template is the one identified path from a Coder principal to the host docker # socket, so the agent gets read-only template access and full workspace access. # Excluded on purpose: coder_create_template, coder_create_template_version, # coder_update_template_active_version, coder_delete_template, # coder_delete_task, coder_upload_tar_file. ALLOWED="coder_get_authenticated_user,\ coder_list_workspaces,coder_get_workspace,coder_create_workspace,coder_create_workspace_build,\ coder_workspace_bash,coder_workspace_ls,coder_workspace_read_file,coder_workspace_write_file,\ coder_workspace_edit_file,coder_workspace_edit_files,coder_workspace_list_apps,\ coder_workspace_port_forward,coder_get_workspace_agent_logs,coder_get_workspace_build_logs,\ coder_list_templates,coder_get_template,coder_template_version_parameters,\ coder_get_template_version_logs,\ coder_create_task,coder_list_tasks,coder_get_task_status,coder_get_task_logs,\ coder_send_task_input,\ search,fetch" # Foreground on purpose. Backgrounding this (`&` + `wait`) breaks the server: # with job control disabled, bash redirects an async command's stdin from # /dev/null, so the MCP stdio transport sees EOF immediately and the client # reports "MCP tool load failed: Transport closed". Running in the foreground # keeps the client's stdin pipe attached; the child shares this process group, # so group-directed signals reach it directly, and the EXIT trap still shreds # the env file once it exits (including on stdin close). sudo -n docker exec -i --env-file "$ENVF" "$CONTAINER" \ /opt/coder exp mcp server --allowed-tools "$ALLOWED"