Write less server code. Server-less is a projection system for Rust. You write an impl block — plain methods with plain types — and server-less projects it onto arbitrary protocols: HTTP, CLI, WebSocket, MCP, gRPC, and more.
You're not writing a server, a CLI app, or an API. You're writing your logic. Server-less handles the rest.
impl UserService {
pub fn create_user(&self, name: String, email: String) -> Result<User, UserError> {
// This is just your code. No framework, no protocol awareness.
}
}Add attributes to project it:
#[http(prefix = "/api")] // → POST /api/users, GET /api/users/{id}
#[cli(name = "users")] // → users create-user --name X --email Y
#[mcp] // → MCP tools: create_user, get_userEach projection is competitive with hand-written code using the protocol's native library (axum, clap, etc.). That's the quality bar, not the pitch. The pitch is: annotate once, project anywhere.
use server_less::prelude::*;
struct UserService { /* ... */ }
#[http(prefix = "/api")]
#[cli(name = "users")]
#[mcp(namespace = "users")]
#[ws(path = "/ws")]
impl UserService {
/// Create a new user
pub async fn create_user(&self, name: String, email: String) -> Result<User, UserError> {
// your implementation
}
/// Get user by ID
pub async fn get_user(&self, id: String) -> Option<User> {
// your implementation
}
/// List all users
pub fn list_users(&self, limit: Option<u32>) -> Vec<User> {
// your implementation
}
}This generates:
| Protocol | Generated | Usage |
|---|---|---|
| HTTP | http_router() |
Axum router with POST /api/users, GET /api/users/{id}, OpenAPI |
| CLI | cli_command() |
Clap commands: users create-user --name X --email Y |
| MCP | mcp_call() |
Model Context Protocol tools: users_create_user, users_get_user |
| WebSocket | ws_router() |
JSON-RPC 2.0: {"method": "create_user", "params": {...}} |
Generate working server implementations:
| Macro | Protocol | Generated Code | Status |
|---|---|---|---|
#[http] |
REST/HTTP | Axum router + OpenAPI spec | ✅ Production Ready |
#[cli] |
Command Line | Clap subcommands | ✅ Production Ready |
#[mcp] |
Model Context Protocol | Tool schemas + dispatch | ✅ Production Ready |
#[ws] |
WebSocket | JSON-RPC 2.0 over WebSocket | ✅ Stable |
#[jsonrpc] |
JSON-RPC | Standalone JSON-RPC handler | ✅ Stable |
#[graphql] |
GraphQL | Schema + resolvers (async-graphql) | ✅ Working* |
*GraphQL has minor type mapping limitations for complex types
Generate IDL/schema files for cross-language services:
| Macro | Protocol | Output | Status |
|---|---|---|---|
#[grpc] |
gRPC | .proto files (Protocol Buffers) |
✅ Working |
#[capnp] |
Cap'n Proto | .capnp schema files |
✅ Working |
#[thrift] |
Apache Thrift | .thrift IDL files |
✅ Working |
#[smithy] |
AWS Smithy | .smithy model files |
✅ Working |
#[connect] |
Connect RPC | Connect protocol schemas | ✅ Working |
Generate API documentation and contracts:
| Macro | Spec Type | Output | Status |
|---|---|---|---|
#[openrpc] |
OpenRPC | JSON-RPC API specification | ✅ Working |
#[asyncapi] |
AsyncAPI | WebSocket/messaging spec | ✅ Working |
#[jsonschema] |
JSON Schema | JSON Schema definitions | ✅ Working |
#[markdown] |
Markdown | Human-readable API docs | ✅ Working |
| Macro | Purpose | Status |
|---|---|---|
#[derive(ServerlessError)] |
Error code inference + HTTP status mapping | ✅ Working |
| Macro | Purpose | Status |
|---|---|---|
#[serve] |
Compose multiple protocol routers | ✅ Working |
#[route] |
Per-method attribute overrides | ✅ Working |
Total: 18 macros, 171 passing tests, 0 failures
- Impl-first design - Write methods once, derive protocol handlers
- Method naming conventions -
create_*→ POST,get_*→ GET,list_*→ collection, etc. - Return type handling -
Result<T,E>,Option<T>,Vec<T>,(), plainTall mapped correctly - Async support - Both sync and async methods work seamlessly
- SSE streaming -
impl Stream<Item=T>for Server-Sent Events (Rust 2024+ use<>) - Error mapping - Automatic HTTP status codes and error responses
- Doc comments -
///comments become API documentation - Parameter extraction - Automatic path/query/body inference
- Feature gated - Only compile what you need
- Zero runtime overhead - Pure compile-time code generation
[dependencies]
# Get everything (recommended for getting started)
server-less = "0.2"
# Or select specific features
server-less = { version = "0.2", default-features = false, features = ["http", "cli", "mcp"] }| Category | Features |
|---|---|
| Runtime protocols | http, cli, mcp, ws, jsonrpc, graphql |
| Schema generators | grpc, capnp, thrift, smithy, connect |
| Spec generators | openrpc, asyncapi, jsonschema, markdown |
| Convenience | full (all features, default) |
Note: ServerlessError derive is always available (zero deps).
Check out examples/ for working code:
- http_service.rs - REST API with Axum + OpenAPI
- cli_service.rs - CLI application with Clap
- user_service.rs - Multi-protocol (HTTP + CLI + MCP + WS)
- ws_service.rs - WebSocket JSON-RPC server
- streaming_service.rs - SSE streaming over HTTP
Server-less supports SSE streaming by returning impl Stream<Item = T>:
use futures::stream::{self, Stream};
#[http]
impl Service {
/// Stream events to the client
pub fn stream_events(&self, count: u64) -> impl Stream<Item = Event> + use<> {
stream::iter((0..count).map(|i| Event { id: i }))
}
}Important: The + use<> syntax is required for Rust 2024 edition when using impl Trait in return position with streaming. This tells the compiler to capture all generic parameters in scope. Without it, you'll get compilation errors about lifetime capture.
// ✅ Correct - Rust 2024
pub fn stream(&self) -> impl Stream<Item = T> + use<> { ... }
// ❌ Error - Missing use<> in Rust 2024
pub fn stream(&self) -> impl Stream<Item = T> { ... }The generated code automatically wraps your stream in SSE format with proper event handling.
- Runtime protocols: HTTP, CLI, MCP, WebSocket, JSON-RPC, GraphQL
- Schema generators: gRPC, Cap'n Proto, Thrift, Smithy, Connect
- Spec generators: OpenRPC, AsyncAPI, JSON Schema, Markdown docs
#[derive(ServerlessError)]with per-protocol status mapping- Blessed presets:
#[server],#[rpc],#[tool],#[program] - Mount points for nested subcommand/route composition
#[route]/#[response]HTTP overrides#[param(positional)],#[param(short)],#[param(query)]#[server(skip)],#[server(hidden)],#[cli(default)]- CLI output: Display default,
--json,--jq,--output-schema - Iterator / SSE streaming return types
- 466 passing tests
- WebSocket server-push patterns
- Middleware / hooks for cross-cutting concerns (auth, rate limiting, logging)
- IDE code action hints
- API versioning
- Multi-language client generation (TypeScript, Python)
- Production hardening and stability guarantees
Server-less is a projection system, not a framework. The distinction matters:
- Frameworks own your code. You write handlers in their shape, using their types.
- Server-less projects your code. You write plain Rust methods. Attributes are semantic metadata —
#[param(help = "...")]becomes CLI help text and OpenAPI description and MCP tool input docs simultaneously.
Progressive disclosure. The zero-config case should just work. Complexity appears only when you need it. Don't like how server-less handles something? Drop that one derive and write it by hand — everything else still composes.
Prior art: Serde. #[derive(Serialize)] doesn't compete with hand-written JSON serializers. It's a projection from Rust types onto data formats. Server-less does the same thing, from Rust methods onto protocols.
See docs/design/ for detailed design documents.
nix develop # Enter dev shell (optional)
cargo build # Build all crates
cargo test # Run all tests (329 passing)
cargo clippy # Lint checks
cargo expand # Inspect macro expansionserver-less/
├── crates/
│ ├── server-less/ # Main crate (re-exports)
│ ├── server-less-macros/ # Proc macro implementations (18 macros, 5,142 LOC)
│ ├── server-less-core/ # Core traits & error types
│ ├── server-less-parse/ # Shared parsing utilities
│ └── server-less-rpc/ # RPC dispatch utilities
└── docs/
├── design/ # Design documents
└── .vitepress/ # Documentation site
- Design Philosophy - Impl-first approach and naming conventions
- Extension Coordination - How macros compose
- Implementation Notes - Technical decisions
- Iteration Log - Evolution and design decisions
- CLAUDE.md - Development guidelines for AI assistants
Server-less is part of the RHI ecosystem - tools for building composable systems.
Contributions welcome! Please check:
- Run tests:
cargo test - Run clippy:
cargo clippy --all-targets --all-features -- -D warnings - Format code:
cargo fmt --all - Follow conventional commits
See CLAUDE.md for development guidelines.
MIT License - see LICENSE for details.
Inspired by Serde's model: derive macros as a projection interface, not a straitjacket.