mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
Moodle 303-redirects every request whose Host is not $CFG->wwwroot, and an MCP client dialling the internal service name cannot follow that redirect from the container network. The previous accommodation was on the wrong side: the probe sent -H 'Host: <public domain>' and -H 'X-Forwarded-Proto: https' and pinned mcp_probe_host to MOODLE_DOMAIN, so it asserted the endpoint works when spoofed and said nothing about how a real client reaches it. Both headers and the probe host are gone. Acceptance moves into config.php, which reads MOODLE_INTERNAL_HOST -- rendered from MOODLE_MCP_SERVICE_KEY -- and, when the incoming HTTP_HOST matches it, rewrites HTTP_HOST and SERVER_NAME to the wwwroot host before Moodle's redirect logic runs. The comparison splits on ':' so a port cannot defeat the match, and a request from any other host is untouched, leaving the canonical-host protection intact for real traffic. None of that could have taken effect on a deployed instance. The bootstrap seeds the code directory with `cp -an`, which never clobbers, so an existing volume keeps its first-run config.php forever and every later config change is silently discarded. moodle_refresh_config now copies the deployment-managed config.php on every start, which is what makes the rewrite above reachable at all. Not verified: no MCP call has been observed succeeding against the internal name since the change. This is also the same failure class that ended the last deploy on another role, where the client already shared the provider's network and had the right alias registered, yet the DNS search suffix still resolved the single-label name to the public ingress. If that applies here too, the Host fix is correct and the request still never arrives -- and removing the spoofing headers will surface it as a failure where the probe previously passed. Two further consequences worth stating: config.php is now overwritten on every start, so an operator edit inside the container is discarded; and the HTTP_HOST rewrite runs in the hot path of every page load, narrow though its condition is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
28 lines
1.1 KiB
YAML
28 lines
1.1 KiB
YAML
---
|
|
- name: "🩺 List the MCP tools with the minted bearer"
|
|
ansible.builtin.shell: | # nocheck: container-exec-resolver runtime-resolved CID, delegated to the hosting node
|
|
container exec -i {{ MOODLE_MCP_LOCAL_CID | quote }} \
|
|
{{ lookup('timeout', 30) | curl(5, 0) }} -f \
|
|
-X POST \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'Accept: application/json' \
|
|
-H {{ ('Authorization: Bearer ' ~ moodle_mcp_token) | quote }} \
|
|
--data-binary {{ MOODLE_MCP_TOOLS_LIST_PAYLOAD | to_json | quote }} \
|
|
{{ MOODLE_MCP_URL | quote }}
|
|
register: moodle_mcp_tools_probe
|
|
changed_when: false
|
|
retries: 30
|
|
delay: 2
|
|
until: moodle_mcp_tools_probe.rc == 0 and '"tools"' in moodle_mcp_tools_probe.stdout
|
|
delegate_to: "{{ MOODLE_MCP_HOST_NODE }}"
|
|
run_once: true
|
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
|
|
|
- name: "🛑 Refuse an MCP surface that exposes no tool"
|
|
ansible.builtin.assert:
|
|
that:
|
|
- (moodle_mcp_tools_probe.stdout | from_json).result.tools | length > 0
|
|
fail_msg: >-
|
|
{{ MOODLE_MCP_URL }} answered the bearer but listed no tool, so every MCP
|
|
client would connect to an empty surface.
|