Skip to content

Agent Gateway: resolve_image + deploy_ephemeral MCP tools (standalone scalable server) - #202

Open
vsilent wants to merge 19 commits into
devfrom
feature/agent-gateway
Open

Agent Gateway: resolve_image + deploy_ephemeral MCP tools (standalone scalable server)#202
vsilent wants to merge 19 commits into
devfrom
feature/agent-gateway

Conversation

@vsilent

@vsilent vsilent commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

What

A standalone, independently-scalable agent-gateway MCP server exposing two tools that give an AI agent capabilities it structurally lacks:

  • resolve_image(reference) — ground truth about a Docker image (exists, digest, size, architectures, recent tags, official/pinned, grade). Kills the trydirect/redis-style hallucination. Also public/unauthenticated at POST /public/resolve_image.
  • deploy_ephemeral(compose, ttl) — run a docker-compose on a throwaway cloud box, return a live URL + logs, auto-teardown after a TTL. The safety-gate + quota + TTL policy is live and tested; provisioning itself is stubbed (M3) pending RabbitMQ/Vault/install-service wiring.

Design

  • crates/agent-tools — pure, mockable core (ref parsing, ground-truth assembly reusing td_audit, TTL/quota/compose-safety-gate). cargo test -p agent-tools runs with no DB/cloud. 19 unit tests, TDD.
  • src/mcp/tools/{resolve_image,deploy_ephemeral}.rs — thin ToolHandlers over the crate; registered in ToolRegistry (so they're on the main server's /mcp too). deploy_ephemeral is MFA-gated.
  • src/bin/agent_gateway.rs + startup::run_agent_gateway — its own actix server (/health + /mcp + /public/resolve_image) on AGENT_GATEWAY_BIND (default :4600), reusing the same auth/casbin. Self-heals its anonymous casbin grants at startup.
  • FreshVmController: SandboxController — the provisioning seam; v1 reuses the OpenTofu→Hetzner deploy path, v2 = Kata/Firecracker warm pool (both behind the trait).

Discovery / distribution

  • Public resolve_image HTTP endpoint (zero-signup), server.json manifest, CONNECT_YOUR_AGENT.md (mcp.json snippets), and a Docker MCP Catalog submission draft.

CI/CD

  • .github/workflows/agent-gateway.yml: crate tests + binary build on PR; builds & pushes trydirect/agent-gateway:<branch> on push. Image compiles inside rust:bookworm (glibc-matched runtime).
  • Also added to rust.yml release artifacts.

Testing

  • cargo test -p agent-tools (pure, no infra).
  • docker-compose.agent-gateway.yml + .env.agent-gateway.example — verified on a dev box: resolve_image returns real multi-arch ground truth; /health 200.

Deliberately out of scope (follow-ups)

  • M3: real deploy_ephemeral provisioning + TTL reaper (needs live MQ/Vault/install-service).
  • MCP /mcp anonymous exposure (currently needs a user JWT).

🤖 Generated with Claude Code

robotizeit and others added 19 commits July 28, 2026 12:02
New workspace crate `agent-tools` holding the testable core for the two
agent-facing MCP tools, compiled/tested apart from the server:

- image: parse_image_ref (Docker Hub normalization: library/, tag vs digest,
  registry-qualified, official/pinned), assemble() -> ResolvedImage reusing
  td_audit::image::audit_image for a grade; ImageResolver trait for injected
  registry fetch.
- sandbox: clamp_ttl / quota_check / gate_compose (privileged, docker.sock, host
  net — reuses td_audit compose parser) / select_expired (reaper); SandboxSpec/
  Handle/Status DTOs + SandboxController trait; launch_sandbox orchestrator.

19 unit tests, written test-first (RED->GREEN), all against mock traits.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
- AGENT_GATEWAY_SETUP.md: developer guide to stand up the gateway —
  prerequisites, env/config, build/run, MCP tool usage, the TTL reaper, cost/
  abuse guardrails, and the v2 Kata/Firecracker warm-pool fast path.
- AGENT_GATEWAY_PLAN.md: the approved implementation plan, checked in.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
ResolveImageTool (ToolHandler) wraps the pure agent_tools::image core with a
DockerHubImageResolver over the Docker Hub v2 API:
- repositories/{repo}/ -> existence + last_pushed
- tags?ordering=last_updated -> recent tags
- tags/{tag}/images -> architectures (with variant), size, digest
Returns the ResolvedImage JSON (exists/official/pinned/digest/size/arch/tags/
grade). Registered in ToolRegistry::new(), so it is immediately callable on the
existing /mcp WebSocket. CVE summary (Trivy) is a follow-up.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
New `agent-gateway` binary + startup::run_agent_gateway: a lean, independently
deployable actix server that serves only /health and the MCP WebSocket at /mcp,
reusing the same auth + casbin middleware and the shared ToolRegistry. Binds
AGENT_GATEWAY_BIND (default 0.0.0.0:4600) so agent traffic scales apart from the
core API. resolve_image is live on it today; deploy_ephemeral lands next.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
DeployEphemeralTool runs the full tested policy path from agent_tools::sandbox:
compose safety-gate (no privileged / docker.sock / host net) -> per-user quota
(SANDBOX_MAX_CONCURRENT_PER_USER) -> TTL clamp (SANDBOX_*_TTL_SECS) -> launch via
the SandboxController seam. Adds active_count() to the trait. FreshVmController is
the production impl over the existing OpenTofu->Hetzner deploy path; provisioning
lands in M3, so launch reports "not enabled" for now while the tool already
enforces safety + quota. Registered + MFA-gated. Gateway builds.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
.github/workflows/agent-gateway.yml: on push/PR to dev/main (path-filtered to
the agent-gateway sources), a fast DB-less job runs `cargo test -p agent-tools`
and builds the `agent-gateway` binary; on push it builds and pushes
`trydirect/agent-gateway:<branch>` via Dockerfile.agent-gateway (gha-cached).

Dockerfile.agent-gateway: multi-stage image (mirrors the root Dockerfile) that
builds and ships only the agent-gateway binary; runs on :4600.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…n image

- rust.yml: build the agent-gateway release binary alongside server/console/
  stacker-cli and include it in the uploaded artifacts.
- agent-gateway.yml: the build job now compiles the release binary once (cached)
  and uploads it; the docker job downloads it and bakes it into a slim runtime
  image (Dockerfile.agent-gateway is now runtime-only) — no Rust compile inside
  Docker, much faster and cache-friendly.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
POST /public/resolve_image ({"reference":"..."}) returns the ResolvedImage
ground truth with no auth — zero-signup callable, the frictionless top of the
funnel and the tool that fixes image hallucination. Shares resolve_reference()
with the MCP tool so both behave identically. Anonymous access granted via a
casbin rule for group_anonymous.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
- CONNECT_YOUR_AGENT.md: how AIs discover the tools — the zero-signup public
  resolve_image curl, and MCP config snippets (mcp.json) for Claude Desktop /
  Cursor / Windsurf / VS Code / Cline, anonymous vs token, and what the agent
  should do with each tool.
- DOCKER_MCP_CATALOG.md: submission draft for the Docker MCP Catalog (the
  highest-fit channel) + the official MCP registry and client marketplaces,
  with the server/tool metadata and a submission checklist.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…ingestion

Machine-readable server manifest (MCP registry schema) describing the gateway:
name, description, repository, the hosted WebSocket remote (wss://mcp.try.direct/mcp,
optional bearer), the OCI package (trydirect/agent-gateway) with sandbox env vars,
and the two tools (resolve_image anonymous, deploy_ephemeral bearer). Registries/
catalogs ingest this; keep it the single source of truth. Field names may need
tweaks to the current registry schema version.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
- agent-gateway.yml: also build & push the image on push to feature/agent-gateway
  (and via workflow_dispatch); sanitize the ref for a valid Docker tag
  (feature/agent-gateway -> feature-agent-gateway).
- Dockerfile.agent-gateway: bake ./migrations so `sqlx migrate run` works from
  the image (needed for casbin policy + the public resolve_image grant).
- docker-compose.agent-gateway.yml: self-contained test stack — Postgres named
  stackerdb (matches baked config), one-shot migrate, gateway on :4600, optional
  redis. Drop-in for a dev environment.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Add .env.agent-gateway.example documenting the vars the gateway actually uses
(image tag, host port, sandbox quotas) with the working endpoints. Parametrize
the compose to read them with defaults; drop the unused redis service/REDIS_URL
(resolve_image uses a plain HTTP client, no cache) so the example isn't
misleading.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…erdb/stackeredis

Rewrite the test compose to attach to the external `trydirect_default` /
`trydirect-network` networks and talk to the already-running `stackerdb`
(Postgres) and `stackeredis` — no throwaway DB. The image's baked
configuration.yaml already points host=stackerdb, so DNS resolves to the real DB
on the shared network (same wiring as stacker). The one-shot migrate applies just
this branch's new casbin grant (idempotent). DATABASE_URL overridable; optional
configuration.yaml mount documented for non-default creds.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The prebuilt binary was compiled on ubuntu-latest (24.04, glibc 2.39) and baked
into debian:bookworm-slim (glibc 2.36), so it failed at runtime with
"GLIBC_2.38 not found". Pin the build job to ubuntu-22.04 (glibc 2.35 <= 2.36)
so the binary runs on the runtime base.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Pinning the builder to ubuntu-22.04 restored the ubuntu-latest (24.04) rust-cache
into a 22.04 job (same "Linux" key), yielding incompatible artifacts and
"error[E0463]: can't find crate for sqlx". Give the cache a runner-specific key
so 22.04 builds against its own clean cache.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The bake-prebuilt approach coupled the binary's glibc to the CI runner:
ubuntu-latest (24.04, glibc 2.39) failed on the debian runtime (2.36), and
pinning to ubuntu-22.04 broke the build (sqlx link failure specific to that
runner). Switch back to a multi-stage Docker build that compiles inside
rust:bookworm and runs on debian:bookworm-slim, so glibc always matches and the
image doesn't depend on the runner env at all. The workflow's fast test job
stays on ubuntu-latest; the docker job builds the image directly (gha-cached).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The authorization wrap gates every route, so /health and /public/resolve_image
returned a bare 403 when the casbin grant wasn't in the DB (custom composes that
skip the migrate service). run_agent_gateway now inserts the two group_anonymous
grants (idempotent) before the enforcer loads policy, so the gateway is reachable
regardless of migrations. Also add /health to the migration for parity.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
curl -d defaults to form content-type, which the Json extractor rejected with
"Content type error" (400). Parse the JSON body content-type-agnostically via
web::Bytes, and accept a ?reference= query param as a fallback, so agents (and
plain curl) aren't tripped by headers.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…nges

The lenient /public/resolve_image change lives in src/routes/agent_public.rs,
which wasn't in the path filter, so it didn't rebuild the image. Add it and
migrations/** (baked into the image) to both push and PR triggers.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants