server-less 0.4.8

Composable derive macros for common Rust patterns
Documentation

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

Blessed presets (batteries-included, use these to get started):

Macro Combines Feature
#[server] #[http] + #[serve(http)] http
#[program] #[cli] + #[markdown] cli
#[tool] #[mcp] + #[jsonschema] mcp
#[rpc] #[jsonrpc] + #[openrpc] + #[serve(jsonrpc)] jsonrpc

À la carte macros (explicit composition):

Macro Protocol Generated Methods
#[http] HTTP/REST http_router(), openapi_spec()
#[cli] Command Line cli_command(), cli_run(), cli_run_async()
#[mcp] MCP mcp_tools(), mcp_call(), mcp_call_async()
#[ws] WebSocket ws_router(), ws_handle_message(), ws_handle_message_async()
#[jsonrpc] JSON-RPC 2.0 jsonrpc_router(), jsonrpc_methods()
#[graphql] GraphQL async-graphql integration
#[grpc] gRPC .proto schema generation

Cross-cutting attributes:

Macro Purpose
#[app(...)] Attach protocol-neutral metadata (name, description, version, homepage)
#[derive(Config)] Generate config loading from env vars, TOML files, and defaults
#[derive(ServerlessError)] Derive IntoErrorCode + Display + Error for error enums
#[route(...)] Per-method HTTP overrides (method, path, skip, hidden)
#[response(...)] Per-method response customization
#[param(...)] Per-parameter metadata (name, default, location, env, help)

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") }])
    }
}

Application Metadata

#[app] attaches protocol-neutral metadata consumed by all protocol macros on the same impl:

use server_less::prelude::*;
use server_less::{app, __app_meta};

#[derive(Clone)]
struct MyApi;

#[app(name = "myapi", description = "My API", version = "1.0.0")]
#[server]
impl MyApi {
    pub fn hello(&self) -> String { String::from("hi") }
}

Preset macros also accept metadata inline:

use server_less::prelude::*;

#[derive(Clone)]
struct MyApi;

#[server(name = "myapi", description = "My API")]
impl MyApi {
    pub fn hello(&self) -> String { String::from("hi") }
}

Config Management

#[derive(Config)] generates config loading from env vars, TOML files, and defaults:

use server_less::prelude::*;
# #[cfg(feature = "config")]
use server_less::{Config as ConfigTrait, ConfigSource};

# #[cfg(feature = "config")]
#[derive(server_less::Config)]
struct AppConfig {
    #[param(default = "localhost")]
    host: String,
    #[param(default = 8080)]
    port: u16,
    #[param(env = "DATABASE_URL")]
    database_url: String,
}

Pass the config type to #[server] to add a config subcommand and wire it into serve:

use server_less::prelude::*;

#[derive(Clone)]
struct MyService;

// #[server(config = AppConfig)]
#[server]
impl MyService {
    pub fn hello(&self) -> String { String::from("hi") }
}

Feature Flags

Enable only what you need:

[dependencies]
server-less = { version = "0.4", default-features = false, features = ["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)
  • jsonrpc - JSON-RPC 2.0 macro
  • graphql - GraphQL macro (requires async-graphql)
  • grpc - gRPC schema generation
  • config - #[derive(Config)] for config loading (requires toml)
  • full - All features (default)