Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
363c217
feat(agent-tools): scaffold pure-logic crate for agent MCP tools (TDD)
robotizeit Jul 28, 2026
a90016e
docs(agent-gateway): infra setup HOWTO + saved implementation plan
robotizeit Jul 28, 2026
740dee3
feat(mcp): resolve_image ground-truth tool (real Docker Hub resolver)
robotizeit Jul 29, 2026
6596357
feat(agent-gateway): standalone scalable MCP server binary
robotizeit Jul 29, 2026
d788371
feat(mcp): deploy_ephemeral tool + SandboxController seam (M2)
robotizeit Jul 29, 2026
1c793e9
ci(agent-gateway): auto-build workflow + standalone Docker image
robotizeit Jul 29, 2026
46aaa65
ci(agent-gateway): add to rust.yml artifacts + bake prebuilt binary i…
robotizeit Jul 29, 2026
0926b60
feat(agent-gateway): public unauthenticated resolve_image endpoint
robotizeit Jul 29, 2026
48e1620
docs(agent-gateway): connect-your-agent guide + Docker MCP Catalog draft
robotizeit Jul 29, 2026
ae572b6
docs(agent-gateway): canonical server.json manifest for MCP registry …
robotizeit Jul 29, 2026
2613374
ci+compose(agent-gateway): build image on this branch + test compose
robotizeit Jul 30, 2026
ca647ee
compose(agent-gateway): .env example + parametrize tag/port/quotas
robotizeit Jul 30, 2026
d78ec56
compose(agent-gateway): join external trydirect networks, reuse stack…
robotizeit Jul 30, 2026
0e86d2c
ci(agent-gateway): pin build runner to ubuntu-22.04 (glibc fix)
robotizeit Jul 30, 2026
1473ff8
ci(agent-gateway): namespace rust-cache to fix cross-runner corruption
robotizeit Jul 30, 2026
a9ec557
ci(agent-gateway): compile in rust:bookworm (bulletproof glibc fix)
robotizeit Jul 30, 2026
8cc2eed
fix(agent-gateway): self-heal anonymous casbin grants at startup
robotizeit Jul 30, 2026
4bc40cb
fix(agent-gateway): make /public/resolve_image lenient on input
robotizeit Jul 30, 2026
10581e8
ci(agent-gateway): trigger on routes/agent_public.rs + migrations cha…
robotizeit Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .env.agent-gateway.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Example env for docker-compose.agent-gateway.yml
# cp .env.agent-gateway.example .env
# docker compose -f docker-compose.agent-gateway.yml up
#
# Docker Compose auto-loads a file named `.env` next to the compose file for
# ${VAR} substitution. Every value below has a sane default baked into the
# compose, so an empty/absent .env still works — override only what you need.

# --- Which image to run ------------------------------------------------------
# The tag this branch's CI publishes. Use a different tag once merged to dev/main.
AGENT_GATEWAY_TAG=feature-agent-gateway

# Host port to expose the gateway on (container always listens on 4600).
HOST_PORT=4600

# --- Reusing the running TryDirect stack ------------------------------------
# The compose joins the external trydirect networks and talks to the existing
# `stackerdb`/`stackeredis`. The gateway itself reads DB creds from the baked
# configuration.yaml (host=stackerdb, db=stacker, postgres/postgres). Only the
# one-shot `migrate` uses DATABASE_URL — override it if your stackerdb differs
# (and mount a matching configuration.yaml for the gateway; see the compose).
DATABASE_URL=postgres://postgres:postgres@stackerdb:5432/stacker

# --- Sandbox quotas (deploy_ephemeral only) ---------------------------------
# These gate deploy_ephemeral's safety/quota checks. Provisioning itself is M3
# (needs RabbitMQ/Vault/install-service), so these don't spend money yet.
SANDBOX_DEFAULT_TTL_SECS=1800 # 30m
SANDBOX_MAX_TTL_SECS=7200 # 2h
SANDBOX_MAX_CONCURRENT_PER_USER=3

# --- What actually works in this test image ---------------------------------
# GET /health
# POST /public/resolve_image {"reference":"redis:7-alpine"} (no auth, real data)
# WS /mcp (needs a user JWT; resolve_image is anonymous there too)
#
# Postgres creds are fixed to match the image's baked configuration.yaml
# (host=stackerdb, db=stacker, postgres/postgres) — don't change them here.
83 changes: 83 additions & 0 deletions .github/workflows/agent-gateway.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Agent Gateway

on:
workflow_dispatch: {}
push:
branches: [dev, main, feature/agent-gateway]
paths:
- "crates/agent-tools/**"
- "crates/td-audit/**"
- "src/mcp/**"
- "src/routes/agent_public.rs"
- "migrations/**"
- "src/bin/agent_gateway.rs"
- "src/startup.rs"
- "Cargo.toml"
- "Cargo.lock"
- "Dockerfile.agent-gateway"
- ".github/workflows/agent-gateway.yml"
pull_request:
branches: [dev, main]
paths:
- "crates/agent-tools/**"
- "crates/td-audit/**"
- "src/mcp/**"
- "src/routes/agent_public.rs"
- "migrations/**"
- "src/bin/agent_gateway.rs"
- "src/startup.rs"
- "Dockerfile.agent-gateway"
- ".github/workflows/agent-gateway.yml"

jobs:
# Fast, DB-less feedback: the pure crate + that the standalone binary compiles.
test:
name: Test crate + build binary
runs-on: ubuntu-latest
env:
SQLX_OFFLINE: true
steps:
- uses: actions/checkout@v4
- name: Install protoc
run: sudo apt-get update -qq && sudo apt-get install -y protobuf-compiler
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: Swatinem/rust-cache@v2
- name: Unit-test the pure agent-tools crate (no DB/cloud)
run: cargo test -p agent-tools --verbose
- name: Build the agent-gateway binary (offline)
run: cargo build --bin agent-gateway --verbose

# Build + push the image on push to dev/main/feature (not PRs). The Docker
# build compiles inside rust:bookworm, so glibc matches the runtime — no
# coupling to the runner's glibc.
docker:
name: Build & push image
needs: test
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set image tag
id: tags
# Sanitize the ref for a valid Docker tag (feature/x -> feature-x).
run: |
TAG="$(echo "${{ github.ref_name }}" | tr '/' '-')"
echo "tags=trydirect/agent-gateway:${TAG}" >> "$GITHUB_OUTPUT"
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile.agent-gateway
push: true
tags: ${{ steps.tags.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
4 changes: 4 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,16 @@ jobs:
- name: Build stacker-cli (release)
run: cargo build --release --target ${{ matrix.target }} --bin stacker-cli --verbose

- name: Build agent-gateway (release)
run: cargo build --release --target ${{ matrix.target }} --bin agent-gateway --verbose

- name: Prepare binaries
run: |
mkdir -p artifacts
cp target/${{ matrix.target }}/release/server artifacts/server
cp target/${{ matrix.target }}/release/console artifacts/console
cp target/${{ matrix.target }}/release/stacker-cli artifacts/stacker-cli
cp target/${{ matrix.target }}/release/agent-gateway artifacts/agent-gateway
tar -czf ${{ matrix.artifact_name }}.tar.gz -C artifacts .
- name: Upload binaries
uses: actions/upload-artifact@v4
Expand Down
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = "2021"
default-run= "server"

[workspace]
members = ["crates/pipe-adapter-sdk", "crates/pipe-adapter-mail", "crates/td-audit"]
members = ["crates/pipe-adapter-sdk", "crates/pipe-adapter-mail", "crates/td-audit", "crates/agent-tools"]
resolver = "2"

[lib]
Expand All @@ -28,6 +28,10 @@ name = "stacker-cli"
path = "src/bin/agent_executor.rs"
name = "agent-executor"

[[bin]]
path = "src/bin/agent_gateway.rs"
name = "agent-gateway"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
Expand All @@ -39,6 +43,7 @@ config = "0.13.4"
reqwest = { version = "0.11.23", features = ["json", "blocking", "stream", "native-tls"] }
serde = { version = "1.0.195", features = ["derive"] }
td-audit = { path = "crates/td-audit" }
agent-tools = { path = "crates/agent-tools" }
tokio = { version = "1.28.1", features = ["full"] }
tracing = { version = "0.1.40", features = ["log"] }
tracing-bunyan-formatter = "0.3.8"
Expand Down
56 changes: 56 additions & 0 deletions Dockerfile.agent-gateway
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# syntax=docker/dockerfile:1.4
# Standalone image for the agent-gateway MCP server.
#
# Multi-stage: compile INSIDE rust:bookworm and run on debian:bookworm-slim, so
# the binary's glibc always matches the runtime (no coupling to the CI runner's
# glibc — which caused "GLIBC_2.38 not found" when baking an ubuntu-built binary).
FROM rust:bookworm AS builder

RUN apt-get update && apt-get install --no-install-recommends -y \
protobuf-compiler libprotobuf-dev libssl-dev \
&& rm -rf /var/lib/apt/lists/*

RUN cargo install sqlx-cli --no-default-features --features native-tls,postgres

WORKDIR /app

# Manifests + offline sqlx cache (compile-time checked queries without a DB).
COPY ./Cargo.toml .
COPY ./Cargo.lock .
COPY ./build.rs .
COPY ./rustfmt.toml .
COPY ./docker/local/.env .
COPY ./docker/local/configuration.yaml .
COPY .sqlx .sqlx/
COPY ./proto ./proto
COPY ./tests/bdd.rs ./tests/bdd.rs

COPY ./src ./src
COPY ./crates ./crates
COPY ./scenarios ./scenarios

ENV SQLX_OFFLINE=true
RUN cargo build --release --bin agent-gateway

# ---- runtime ----
FROM debian:bookworm-slim AS production

RUN apt-get update && apt-get install --no-install-recommends -y \
libssl3 ca-certificates \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app
RUN mkdir ./files && chmod 0777 ./files

COPY --from=builder /app/target/release/agent-gateway /app/agent-gateway
COPY --from=builder /usr/local/cargo/bin/sqlx /usr/local/bin/sqlx
COPY docker/local/configuration.yaml /app/configuration.yaml
COPY docker/local/.env /app/.env
COPY access_control.conf.dist /app/access_control.conf
# Baked migrations so `sqlx migrate run` works from the image.
COPY migrations /app/migrations

ENV AGENT_GATEWAY_BIND=0.0.0.0:4600
EXPOSE 4600

ENTRYPOINT ["/app/agent-gateway"]
16 changes: 16 additions & 0 deletions crates/agent-tools/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "agent-tools"
version = "0.1.0"
edition = "2021"
description = "Pure-logic core for TryDirect's agent-facing MCP tools (resolve_image ground truth, deploy_ephemeral sandbox orchestration). Heavy I/O is injected via traits so this compiles/tests apart from the Actix/sqlx server."

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
thiserror = "1"
async-trait = "0.1"
td-audit = { path = "../td-audit" }

[dev-dependencies]
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
24 changes: 24 additions & 0 deletions crates/agent-tools/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//! Error type shared by the agent tools. Kept small and stringly-mappable so MCP
//! `ToolHandler`s can turn it into a JSON-RPC error message.

use thiserror::Error;

#[derive(Debug, Error, PartialEq)]
pub enum AgentToolError {
#[error("invalid input: {0}")]
InvalidInput(String),

#[error("quota exceeded: {0}")]
QuotaExceeded(String),

/// The agent-supplied compose was rejected by the safety gate before any
/// infrastructure was provisioned.
#[error("compose rejected: {0}")]
ComposeRejected(String),

/// A downstream (registry, MQ, cloud, DB) operation failed.
#[error("backend error: {0}")]
Backend(String),
}

pub type Result<T> = std::result::Result<T, AgentToolError>;
Loading
Loading