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.
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.
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 +15Wrap 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 +20The /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 +20Use 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 +30Serve 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 +15Type-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 +20Simple 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 +20Derive 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 +10Tower 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.
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
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.