Agent Gateway: resolve_image + deploy_ephemeral MCP tools (standalone scalable server) - #202
Open
vsilent wants to merge 19 commits into
Open
Agent Gateway: resolve_image + deploy_ephemeral MCP tools (standalone scalable server)#202vsilent wants to merge 19 commits into
vsilent wants to merge 19 commits into
Conversation
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]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 thetrydirect/redis-style hallucination. Also public/unauthenticated atPOST /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 reusingtd_audit, TTL/quota/compose-safety-gate).cargo test -p agent-toolsruns with no DB/cloud. 19 unit tests, TDD.src/mcp/tools/{resolve_image,deploy_ephemeral}.rs— thinToolHandlers over the crate; registered inToolRegistry(so they're on the main server's/mcptoo).deploy_ephemeralis MFA-gated.src/bin/agent_gateway.rs+startup::run_agent_gateway— its own actix server (/health+/mcp+/public/resolve_image) onAGENT_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
resolve_imageHTTP endpoint (zero-signup),server.jsonmanifest,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 & pushestrydirect/agent-gateway:<branch>on push. Image compiles insiderust:bookworm(glibc-matched runtime).rust.ymlrelease 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_imagereturns real multi-arch ground truth;/health200.Deliberately out of scope (follow-ups)
deploy_ephemeralprovisioning + TTL reaper (needs live MQ/Vault/install-service)./mcpanonymous exposure (currently needs a user JWT).🤖 Generated with Claude Code