Server-less - Composable derive macros for Rust
Server-less takes an impl-first approach: write your Rust methods, and derive macros project them into various protocols (HTTP, CLI, MCP, WebSocket).
Quick Start
use server_less::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct User { name: String, email: String }
#[derive(Debug, ServerlessError)]
enum UserError { NotFound }
struct UserService;
#[mcp]
impl UserService {
/// Create a new user
async fn create_user(&self, name: String, email: String) -> Result<User, UserError> {
Ok(User { name, email })
}
/// List all users
async fn list_users(&self, limit: Option<u32>) -> Vec<User> {
let _ = limit;
vec![]
}
}
This generates:
- MCP: Tools
create_user,list_users(Model Context Protocol)
Available Macros
| Macro | Protocol | Generated Methods |
|---|---|---|
#[http] |
HTTP/REST | http_router(), openapi_spec() |
#[cli] |
Command Line | cli_command(), cli_run() |
#[mcp] |
MCP | mcp_tools(), mcp_call(), mcp_call_async() |
#[ws] |
WebSocket | ws_router(), ws_handle_message(), ws_handle_message_async() |
Naming Conventions
Method names infer HTTP methods and CLI subcommand structure:
| Prefix | HTTP | CLI |
|---|---|---|
create_*, add_* |
POST | <cmd> create-* |
get_*, fetch_* |
GET (single) | <cmd> get-* |
list_*, find_* |
GET (collection) | <cmd> list-* |
update_*, set_* |
PUT | <cmd> update-* |
delete_*, remove_* |
DELETE | <cmd> delete-* |
Return Types
| Type | HTTP | CLI | MCP/WS |
|---|---|---|---|
T |
200 + JSON | stdout JSON | JSON result |
Option<T> |
200 or 404 | stdout or exit 1 | result or null |
Result<T, E> |
200 or error | stdout or stderr | result or error |
() |
204 | silent | {"success": true} |
impl Stream<Item=T> |
SSE | N/A | N/A |
Async Methods
All macros support async methods:
use server_less::prelude::*;
struct MyService;
#[mcp]
impl MyService {
/// Sync method - works with mcp_call() and mcp_call_async()
pub fn sync_method(&self) -> String {
String::from("hello")
}
/// Async method - use mcp_call_async() for proper await
pub async fn async_method(&self) -> String {
String::from("hello async")
}
}
#[tokio::main]
async fn main() {
let service = MyService;
// Sync call (errors on async methods)
service.mcp_call("sync_method", serde_json::json!({}));
// Async call (awaits async methods properly)
service.mcp_call_async("async_method", serde_json::json!({})).await;
}
SSE Streaming (HTTP)
Return impl Stream<Item=T> for Server-Sent Events:
use server_less::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize)]
struct Event { message: String }
#[derive(Clone)]
struct StreamService;
#[http]
impl StreamService {
// Note: Rust 2024 requires `+ use<>` to avoid lifetime capture
pub fn stream_events(&self) -> impl futures::Stream<Item = Event> + use<> {
futures::stream::iter(vec![Event { message: String::from("hello") }])
}
}
Feature Flags
Enable only what you need:
[]
= { = "0.2", = false, = ["http", "cli"] }
Available features:
mcp- MCP macro (no extra deps)http- HTTP macro (requires axum)cli- CLI macro (requires clap)ws- WebSocket macro (requires axum, futures)full- All features (default)