Skip to main content
TutorialSystems LanguagesPart 4 of Series

Building Agent-Ready APIs with Go and Rust

The fourth in our framework tutorial series. Go and Rust are the performance backbone of the agent economy — sub-millisecond latency, memory safety, and the ability to handle 100,000+ concurrent agent connections. Here are the copy-paste patterns that make your Go or Rust API agent-ready in 30 minutes.

AH
AgentHermes Research
April 15, 202614 min read

Why Go and Rust Excel at Agent-Ready APIs

We have covered Next.js, Django and Flask, and Express and Fastify in previous tutorials. Go and Rust bring something different to agent readiness: raw performance that directly boosts two of the highest-weighted scoring dimensions.

D8 Reliability carries a 0.13 weight in the Agent Readiness Score — the second-highest single dimension. D7 Security carries 0.12. Systems languages inherently score higher on both because they produce services with predictable latency, no garbage collection pauses during agent requests, efficient memory usage under high concurrency, and compile-time safety guarantees that eliminate entire classes of vulnerabilities.

If your API serves thousands of agents simultaneously — each making multiple tool calls per session — Go and Rust are where you want to be.

Metric
Go
Rust
Score Impact
Latency
Sub-millisecond handler execution
Zero-cost abstractions, no GC pauses
D8 Reliability: +15-25 points
Memory Safety
Garbage collected, no buffer overflows
Compile-time ownership, zero runtime cost
D7 Security: +10-20 points
Concurrency
Goroutines handle 100K+ concurrent agents
Tokio async handles extreme concurrency
D8 Reliability: +10-15 points
Binary Size
Single static binary, no runtime deps
Single static binary, minimal footprint
D8 Reliability: +5-10 points

Go Patterns: net/http + chi

Go’s standard library is powerful enough for production APIs. Add chi for routing and swaggo for OpenAPI generation. Four patterns get you from zero to agent-ready.

Structured Error Middleware

D2 API Quality +15

Wrap every handler with consistent JSON error responses. Agents parse error codes, not HTML error pages.

type APIError struct {
  Error     string `json:"error"`
  Code      string `json:"code"`
  RequestID string `json:"request_id"`
}

func errorMiddleware(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    defer func() {
      if err := recover(); err != nil {
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(500)
        json.NewEncoder(w).Encode(APIError{
          Error: "Internal server error",
          Code:  "INTERNAL_ERROR",
          RequestID: r.Header.Get("X-Request-ID"),
        })
      }
    }()
    next.ServeHTTP(w, r)
  })
}

Health Check Endpoint

D8 Reliability +20

The /healthz convention is standard in Go services. Agents use this to verify uptime before sending requests.

func healthHandler(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  json.NewEncoder(w).Encode(map[string]any{
    "status":  "healthy",
    "version": version,
    "uptime":  time.Since(startTime).String(),
  })
}

// Register: r.Get("/healthz", healthHandler)

OpenAPI with Swaggo

D1 Discovery +25, D2 API Quality +20

Use swaggo/swag to generate OpenAPI specs from Go doc comments. Agents auto-discover your endpoints.

// @title        My Service API
// @version      1.0
// @description  Agent-ready API for my service
// @host         api.example.com
// @BasePath     /v1
// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization

// @Summary      List products
// @Description  Returns all products with pagination
// @Tags         products
// @Accept       json
// @Produce      json
// @Param        limit  query  int  false  "Limit"  default(20)
// @Param        offset query  int  false  "Offset" default(0)
// @Success      200  {array}   Product
// @Failure      400  {object}  APIError
// @Router       /products [get]
func listProducts(w http.ResponseWriter, r *http.Request) { }

Agent Card Serving

D9 Agent Experience +30

Serve agent-card.json at /.well-known/agent-card.json. This is how agents discover your service.

func agentCardHandler(w http.ResponseWriter, r *http.Request) {
  card := map[string]any{
    "name":        "My Service",
    "description": "Agent-ready API for product management",
    "url":         "https://api.example.com",
    "version":     "1.0.0",
    "capabilities": map[string]any{
      "streaming":      false,
      "pushNotifications": false,
    },
    "skills": []map[string]any{
      {
        "id":   "product-search",
        "name": "Product Search",
        "description": "Search products by category, price, or keyword",
      },
    },
  }
  w.Header().Set("Content-Type", "application/json")
  json.NewEncoder(w).Encode(card)
}

// r.Get("/.well-known/agent-card.json", agentCardHandler)

Rust Patterns: Axum + Tower

Axum is the leading Rust web framework, built on Tower middleware and Tokio async. utoipa generates OpenAPI schemas from Rust types — if your struct compiles, your docs are correct.

Custom Error Types with Axum

D2 API Quality +15

Type-safe error handling that automatically serializes to structured JSON. Rust's type system prevents unstructured errors.

use axum::{response::IntoResponse, http::StatusCode, Json};
use serde::Serialize;

#[derive(Serialize)]
struct ApiError {
    error: String,
    code: String,
    request_id: String,
}

enum AppError {
    NotFound(String),
    BadRequest(String),
    Internal(String),
}

impl IntoResponse for AppError {
    fn into_response(self) -> axum::response::Response {
        let (status, code, msg) = match self {
            AppError::NotFound(m) => (StatusCode::NOT_FOUND, "NOT_FOUND", m),
            AppError::BadRequest(m) => (StatusCode::BAD_REQUEST, "BAD_REQUEST", m),
            AppError::Internal(m) => (StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL", m),
        };
        (status, Json(ApiError {
            error: msg, code: code.into(), request_id: String::new(),
        })).into_response()
    }
}

Health Check with Axum

D8 Reliability +20

Simple health endpoint that agents check before every interaction. Include uptime and version.

use std::time::Instant;
use axum::{Json, extract::State};
use serde_json::{json, Value};

struct AppState { start_time: Instant, version: String }

async fn health(State(state): State<AppState>) -> Json<Value> {
    Json(json!({
        "status": "healthy",
        "version": state.version,
        "uptime_secs": state.start_time.elapsed().as_secs(),
    }))
}

// Route: .route("/healthz", get(health))

OpenAPI with utoipa

D1 Discovery +25, D2 API Quality +20

Derive OpenAPI schemas from Rust structs. Compile-time guarantees that your docs match your types.

use utoipa::{OpenApi, ToSchema};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, ToSchema)]
struct Product {
    #[schema(example = "prod_123")]
    id: String,
    #[schema(example = "Diamond Ring")]
    name: String,
    #[schema(example = 2999.99)]
    price: f64,
}

#[utoipa::path(
    get, path = "/v1/products",
    responses(
        (status = 200, description = "List products", body = Vec<Product>),
        (status = 400, description = "Bad request", body = ApiError),
    ),
    params(
        ("limit" = Option<i32>, Query, description = "Max results"),
        ("offset" = Option<i32>, Query, description = "Skip results"),
    )
)]
async fn list_products() -> Json<Vec<Product>> { todo!() }

#[derive(OpenApi)]
#[openapi(paths(list_products), components(schemas(Product, ApiError)))]
struct ApiDoc;

CORS and Auth Tower Middleware

D7 Security +15, D2 API Quality +10

Tower middleware layers handle cross-cutting concerns. Agent platforms need CORS headers to call your API from browsers.

use tower_http::cors::{CorsLayer, Any};
use axum::{middleware, extract::Request, http::HeaderMap};

let cors = CorsLayer::new()
    .allow_origin(Any)
    .allow_methods(Any)
    .allow_headers(Any);

async fn auth_middleware(
    headers: HeaderMap,
    request: Request,
    next: middleware::Next,
) -> Result<impl IntoResponse, AppError> {
    let token = headers.get("authorization")
        .and_then(|v| v.to_str().ok())
        .and_then(|v| v.strip_prefix("Bearer "))
        .ok_or(AppError::BadRequest("Missing auth".into()))?;

    // Validate token...
    Ok(next.run(request).await)
}

// App: .layer(cors).layer(middleware::from_fn(auth_middleware))

The Agent-Ready Checklist for Go and Rust

Eight items. Implement all eight and your API moves from ARL-0 (Dark) to ARL-3 (Silver) or higher. Each maps to a specific scoring dimension.

Structured JSON error responses
D2 API Qualityerror, code, request_id fields
/healthz endpoint
D8 ReliabilityReturns status, version, uptime
OpenAPI spec generation
D1 Discoveryswaggo (Go) or utoipa (Rust)
agent-card.json at /.well-known/
D9 Agent ExperienceName, capabilities, skills
CORS headers
D2 API QualityAllow-Origin, Allow-Methods, Allow-Headers
Auth middleware (Bearer token)
D7 SecurityValidates tokens, returns 401 JSON
Rate limiting middleware
D7 SecurityPer-key limits, 429 with Retry-After
Request ID propagation
D2 API QualityX-Request-ID header on every response

The total effort is approximately 30 minutes for a Go developer and 45 minutes for Rust (Rust’s type system requires more boilerplate upfront, but catches more errors at compile time). Both languages start at a natural advantage on D7 and D8 — you are building on a foundation that is already 15-25 points ahead of interpreted languages on the performance and security dimensions.

Expected Score After Implementation

5-15
Before
55-65
After (minimal)
75-85
After (full + MCP)

A bare Go or Rust API with no agent infrastructure scores 5-15. Add the eight checklist items and you jump to Silver (55-65). Add an MCP server with discoverable tools and you reach Gold (75-85). The systems language advantage means your D7 and D8 scores start higher than equivalent implementations in Node.js or Python.

Run your current API through the AgentHermes scanner to see your baseline, implement these patterns, then scan again to measure the improvement.

Frequently Asked Questions

Should I use Go or Rust for an agent-ready API?

Both are excellent choices. Go is simpler to learn and has faster development velocity — choose it when team productivity matters most. Rust offers stronger compile-time safety guarantees and better raw performance — choose it when your API serves high-frequency agent traffic or handles security-critical operations. Both score identically on agent readiness dimensions.

Do I need OpenAPI if I have an MCP server?

Yes. OpenAPI and MCP serve different purposes. OpenAPI documents your REST API so agents can discover and understand your endpoints. MCP provides a standardized protocol for agent interaction beyond REST. Having both maximizes your D1 Discovery and D9 Agent Experience scores. Many agents use OpenAPI for initial discovery and then connect via MCP for richer interactions.

Which Go router is best for agent readiness?

chi and gin both work well. chi is more idiomatic Go (net/http compatible) and composes middleware cleanly. gin has built-in Swagger support via gin-swagger. For agent readiness specifically, the router matters less than the patterns: structured errors, health endpoints, OpenAPI docs, and agent-card.json. Pick whichever your team knows.

How do I add an MCP server to a Go or Rust API?

For Go, the mcp-go library (github.com/mark3labs/mcp-go) provides a server implementation. For Rust, the mcp-rust-sdk crate is available. Both let you define tools, resources, and prompts alongside your existing REST API. Alternatively, AgentHermes can auto-generate a hosted MCP server from your OpenAPI spec — no code changes needed.

What is the minimum viable agent-ready Go or Rust API?

Five things: (1) A /healthz endpoint returning JSON status. (2) Structured error responses with error, code, and request_id fields. (3) An OpenAPI spec served at /openapi.json or /swagger.json. (4) An agent-card.json at /.well-known/agent-card.json. (5) CORS headers allowing agent platforms to call your API. This takes about 30 minutes to add and gets you from ARL-0 to ARL-2.


Scan your Go or Rust API now

See your Agent Readiness Score across all 9 dimensions. Then use these patterns to jump from Dark to Silver in 30 minutes.


Share this article: