Readme
Agent-First HTTP
Persistent HTTP client for AI agents — one request, one JSON line.
Supported platforms: macOS, Linux, Windows.
Modes (single entrypoint):
--mode cli (default)
--mode pipe
--mode mcp
--mode curl
The key contract for agents is protocol determinism: stdout is always structured JSON, and failures are always structured { " code" : " error" , ... } events with stable error_code values. No human-only text parsing, no mixed output channels, no ad-hoc error shapes.
CLI Mode
The default mode — one request, one JSON response, exit:
afhttp GET https://api.example.com/users
# {"code":"response","status":200,"body":[...],"trace":{"duration_ms":120,...}}
afhttp POST https://api.example.com/users --body '{"name":"Alice","email":"alice@example.com"}'
# {"code":"response","status":201,"body":{"id":42},...}
afhttp GET https://api.example.com/data --header "Authorization: Bearer sk-xxx"
# {"code":"response","status":200,...}
Structured error output examples:
{ " code" : " error" , " error_code" : " connect_timeout" , " error" : " request timed out after 30s" , " retryable" : true , " trace" : { " duration_ms" : 30012 } }
{ " code" : " error" , " error_code" : " invalid_request" , " error" : " invalid --output format 'xml': expected json, yaml, or plain" , " retryable" : false , " trace" : { " duration_ms" : 0 } }
Pipe Mode
For long-lived sessions with connection reuse, concurrent requests, and WebSocket — use afhttp - - mode pipe :
afhttp -- mode pipe << ' EOF '
{"code":"config","host_defaults":{"api.example.com":{"headers":{"x-api-key":"sk-xxx","api-version":"2023-06-01"}}}}
{"code":"request","id":"models","method":"GET","url":"https://api.example.com/v1/models"}
{"code":"request","id":"usage","method":"GET","url":"https://api.example.com/v1/usage"}
{"code":"request","id":"chat","method":"POST","url":"https://api.example.com/v1/messages","body":{"model":"general-chat-model","max_tokens":256,"messages":[{"role":"user","content":"Hello"}],"stream":true},"options":{"chunked":true,"chunked_delimiter":"\n\n"}}
EOF
output:
{ " code" : " config" , " host_defaults" : { " api.example.com" : { " headers" : { " x-api-key" : " [redacted]" , " api-version" : " 2023-06-01" } } } , . . . }
{ " code" : " response" , " id" : " models" , " status" : 200 , " body" : { " data" : [ { " id" : " general-chat-model" , . . . } ] } , " trace" : { " duration_ms" : 92 , " http_version" : " h2" , " redirects" : 0 , " remote_addr" : " 13.32.4.10" } }
{ " code" : " response" , " id" : " usage" , " status" : 403 , " body" : { " error" : { " type" : " permission_error" , " message" : " Your API key does not have permission" } } , " trace" : { " duration_ms" : 87 , " http_version" : " h2" , " redirects" : 0 } }
{ " code" : " chunk_start" , " id" : " chat" , " status" : 200 , " headers" : { " content-type" : " text/event-stream" } }
{ " code" : " chunk_data" , " id" : " chat" , " data" : " event: content_block_delta\n data: {\" delta\" :{\" text\" :\" Hello\" }}" }
{ " code" : " chunk_data" , " id" : " chat" , " data" : " event: content_block_delta\n data: {\" delta\" :{\" text\" :\" there\" }}" }
{ " code" : " chunk_data" , " id" : " chat" , " data" : " event: message_stop\n data: {}" }
{ " code" : " chunk_end" , " id" : " chat" , " trace" : { " duration_ms" : 834 , " chunks" : 8 } }
What just happened:
One bash call — the heredoc sends all requests into one afhttp process; afhttp exits when stdin closes
Auth set once per host — host_defaults applies auth only to matching host; credentials do not leak to other domains
Three requests fired without waiting — models , usage , and chat all in-flight simultaneously
Connection reuse is automatic — requests to the same host can reuse pooled connections without extra agent logic
Out-of-order responses — usage arrived before chat finished; the agent matches by id
Streaming inline — chat delivers events as they arrive, no buffering, no special setup
HTTP errors are data — usage returned 403; afhttp delivers it as code: " response" with status: 403 ; the agent checks status , not exception types or text patterns
MCP Mode
afhttp - - mode mcp runs as a Model Context Protocol server, letting AI tools like Claude Desktop make HTTP requests directly:
{
" mcpServers" : {
" afhttp" : { " command" : " afhttp" , " args" : [ " --mode" , " mcp" ] }
}
}
Claude can then call http_request and http_config tools. See docs/mcp.md for the full setup guide.
curl Compatibility
Use explicit curl mode. afhttp understands a subset of curl flags and returns structured JSON:
afhttp -- mode curl - X POST https://api.example.com/users \
-H " Authorization: Bearer sk-xxx" \
-d ' {"name":"Alice"}'
# {"code":"response","status":201,"body":{"id":42},...}
Install
macOS / Linux — Homebrew
brew install cmnspore/tap/afhttp
Windows — Scoop
scoop bucket add cmnspore https:/ / github.com / cmnspore/ scoop- bucket
scoop install afhttp
Any platform — Cargo
cargo install agent-first-http
Docs
License
MIT