Files
computer-playbook/roles/svc-ai-mcp-adapter/tasks/current_key.yml
kevinveenbirkenbachandClaude Opus 5 683de0ab9a feat(svc-ai-mcp-adapter): add the reusable per-provider MCP adapter
One audited implementation, instantiated once per provider application rather
than deployed as a single process holding every application's credential. Each
instance joins only its provider's network, carries only that provider's
least-privileged credential and serves a distinct endpoint.

Every decision lives in the policy layer, which validates the contract at
startup rather than at first call, so an instance missing a limit, an
allowlist or the schema hash never reaches a client. The server itself only
moves bytes over three JSON-RPC methods, with no stream state to get wrong.
Its upstream header is configurable because only half the providers accept an
RFC 6750 bearer.

The contract probe runs at deploy time rather than in Playwright, because it
needs the provider's real credential and a Playwright trace would capture it.
It proves, over both transports, that an unauthenticated caller is refused,
that a wrongly authenticated one is refused, that the handshake completes,
that the served tool list is exactly the contracted one, and that one
deterministic read answers without an error. Steps one and two are what
separate a guarded endpoint from one that merely happens to be unreachable.

The isolation probe counts the sidecar's network interfaces from inside it.
Reachability cannot be used here: an unknown name does not fail to resolve in
this environment, it falls through to a wildcard resolver that answers every
name with one reachable address.

The role is inert until a provider includes it; those inclusions follow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 14:04:08 +02:00

28 lines
1.2 KiB
YAML

---
# Read the upstream credential the adapter is holding right now.
#
# A provider that mints its own secret hands it back once, so the deployment
# cannot re-derive it. Reading it back is what lets a converged run keep the
# working credential instead of minting a replacement and revoking the one the
# sidecar is using.
#
# Param: mcp_upstream_key_path env file the sidecar reads.
# Sets: MCP_UPSTREAM_CURRENT_KEY the stored credential, or "" when none.
- name: "🔍 Read the upstream credential file of {{ application_id }}"
ansible.builtin.slurp:
src: "{{ mcp_upstream_key_path }}"
register: mcp_upstream_current
failed_when: false
delegate_to: "{{ STACK_HOST }}"
become: true
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" # nocheck: include-uniform-apply-keyword the read runs on the stack host and the set_fact on the controller
- name: "🔑 Publish the credential the {{ application_id }} adapter holds"
ansible.builtin.set_fact:
MCP_UPSTREAM_CURRENT_KEY: >-
{{ (mcp_upstream_current.content | b64decode).split('=', 1)[1] | trim
if mcp_upstream_current.content is defined
and '=' in (mcp_upstream_current.content | b64decode)
else '' }}
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"