mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
The contract check ran as an Ansible task in the middle of every provider deploy, and the two client checks ran the same way. A test that is part of the deploy cannot be run without deploying, cannot be re-run on its own, and reports its verdict as a deploy failure. Twenty-seven task files lose 491 lines to this.
It is now what it always was. Each provider ships files/test/test.sh, templates/test.env.j2 and meta/tests.yml, and test-e2e-cli stages one shared harness beside it: run.sh drives contract.sh in a throwaway container on the provider's own network, then isolation.sh for adapter-fronted providers. The read probe each provider offers moved from the calling task into meta/mcp.yml next to the allowlist it must satisfy, and the schema now rejects a read probe naming a tool outside that allowlist.
The bearer the test presents is the one a client resolves through mcp.credential, via the new mcp_client_bearer lookup over resolve_credential, which moved to utils.roles.applications.mcp so it is the same function mcp_servers renders every client connection with. The probe presented whatever the calling role passed, which for the fifteen adapter-fronted providers is a different secret than any client holds.
Sidecar isolation is read from the engine rather than measured inside the container, so it no longer needs a delegation to the node holding the task: attachment is compared by network name, which drops the gwbridge special case, and a swarm worker says it cannot measure instead of skipping silently.
Retrying is bounded by wall clock against MCP_DEADLINE_SECONDS, which scales through the same lookup('timeout') as the harness budget it has to stay under. A flag that is neither true nor false aborts rather than reading as off.
mcp-provider-probe becomes mcp-provider-cli-test: an enabled provider must ship the test rather than include the task. mcp-probe-bearer becomes mcp-adapter-bearer, tying ADAPTER_BEARER to the declared credential key now that no probe include carries it. A new integration test renders the shared env template for every provider through Ansible's own templar, so a later change to it breaks visibly instead of poisoning twenty deploys quietly.
Hermes and OpenClaw keep their client-side proof as CLI tests of their own.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
69 lines
2.7 KiB
Python
69 lines
2.7 KiB
Python
"""Lookup ``mcp_client_bearer``: the secret a client presents to a provider.
|
|
|
|
"{{ lookup('mcp_client_bearer', 'web-app-gitea') }}"
|
|
-> the bearer an admitted client sends, or "" when none is minted yet
|
|
|
|
Which secret that is depends on ``mcp.credential.source``: an adapter fronts
|
|
its upstream and issues its own bearer under the role's ``secrets.credentials``,
|
|
while a native or plugin provider hands out the token its principal holds in
|
|
the store. Reading the wrong one yields a secret that exists, authenticates
|
|
something, and is rejected by the endpoint under test.
|
|
|
|
The choice is not repeated here: it is ``resolve_credential``, the same
|
|
function ``mcp_servers`` renders every client's connection with, so a test
|
|
built on this lookup measures the credential the deployment actually ships.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from ansible.errors import AnsibleError
|
|
from ansible.plugins.loader import lookup_loader
|
|
from ansible.plugins.lookup import LookupBase
|
|
|
|
from utils.manager.credential_key import CREDENTIALS_KEY, SECRETS_KEY
|
|
from utils.roles.applications.mcp import resolve_credential
|
|
|
|
|
|
class LookupModule(LookupBase):
|
|
def run(
|
|
self,
|
|
terms: list[Any] | None,
|
|
variables: dict[str, Any] | None = None,
|
|
**kwargs: Any,
|
|
) -> list[Any]:
|
|
terms = list(terms or [])
|
|
if len(terms) != 1 or not str(terms[0]).strip():
|
|
raise AnsibleError(
|
|
"mcp_client_bearer: expected the provider's application_id, "
|
|
"e.g. lookup('mcp_client_bearer', 'web-app-gitea')"
|
|
)
|
|
application_id = str(terms[0]).strip()
|
|
vars_ = variables or getattr(self._templar, "available_variables", {}) or {}
|
|
templar = getattr(self, "_templar", None)
|
|
config = lookup_loader.get("config", loader=self._loader, templar=templar)
|
|
|
|
credential = config.run([application_id, "mcp.credential"], variables=vars_)[0]
|
|
if not isinstance(credential, dict):
|
|
raise AnsibleError(
|
|
f"mcp_client_bearer: {application_id} declares no mcp.credential, "
|
|
f"so no client could authenticate against it"
|
|
)
|
|
|
|
owner = str(credential.get("owner") or "").strip()
|
|
users = lookup_loader.get("users", loader=self._loader, templar=templar).run(
|
|
[owner, {}], variables=vars_
|
|
)[0]
|
|
|
|
role_credentials = config.run(
|
|
[application_id, f"{SECRETS_KEY}.{CREDENTIALS_KEY}", {}], variables=vars_
|
|
)[0]
|
|
|
|
token, _owner = resolve_credential(
|
|
{"credential": credential},
|
|
{owner: users} if isinstance(users, dict) else {},
|
|
role_credentials if isinstance(role_credentials, dict) else {},
|
|
)
|
|
return [token]
|