subako-function-sdk 0.2.2

Write Subako function packages in Rust: typed functions become tools, and the program prints its own manifest.
Documentation

subako-function-sdk

Write Subako function packages in Rust. Each #[function] becomes a tool an agent can call:

  • the function's name is the tool's name;
  • its doc comment is the description the model reads;
  • its input and output types, which derive JsonSchema, become the tool's schemas.
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use subako_function_sdk::{ToolError, function, package};

#[derive(Deserialize, JsonSchema)]
struct GreetInput {
    /// Who to greet.
    name: String,
}

#[derive(Serialize, JsonSchema)]
struct Greeting {
    message: String,
}

/// Greet someone by name.
#[function]
fn greet(input: GreetInput) -> Result<Greeting, ToolError> {
    Ok(Greeting { message: format!("Hello, {}!", input.name) })
}

package! {
    name: "hello",
    description: "Says hello.",
    functions: [greet],
}

To show the model an image, return an Answer: the model reads its value as JSON first, then the image. The image travels as an MCP resource_link to volume:///<path beneath /volume>; Core reads the file from the session volume after the call returns, and downscales it to at most 2000 pixels on its long edge.

use subako_function_sdk::{Answer, ToolError, function};

#[derive(Serialize, JsonSchema)]
struct Plot {
    path: String,
}

/// Plot a chart and show it.
#[function]
fn plot(input: PlotInput) -> Result<Answer<Plot>, ToolError> {
    let path = "/volume/tmp/chart.png".to_string();
    // ... write the chart to `path` ...
    Ok(Answer::new(Plot { path: path.clone() }).with_image_file(path))
}

To call an HTTP API, use http::request. The worker sends the request for the function: only https, only to hosts the agent's function grant allows, and with any stored credential the grant names for that origin attached on the way out. An HttpError converts into a ToolError, so ? hands the model the reason.

use subako_function_sdk::http::{self, Request, ResponseBody};

/// Fetch a page's text.
#[function]
fn fetch(input: FetchInput) -> Result<Page, ToolError> {
    let response = http::request(&Request::get(input.url))?;
    match response.body {
        ResponseBody::Inline { text } => Ok(Page { status: response.status, text }),
        ResponseBody::File { .. } => Err(ToolError::new("the body was saved to a file")),
    }
}

The body comes back inline as text, up to http::INLINE_MAX_BYTES. Set output to ResponseOutput::File { path } to stream a larger or binary body into the session volume instead, and send a volume file as the body with RequestBody::File { path }.

Build the module for wasm32-wasip1. The same crate built for your machine prints the package's subako-package.json with cargo run -- --subako-manifest: its ABI, name, and description. Subako learns the tools from the module itself when the package is uploaded.

The protocol

The SDK speaks ABI 1, which any language that compiles to WASI preview 1 can speak too. A module is an MCP server over MCP's stdio transport, on the stateless revision 2026-07-28: one JSON-RPC 2.0 message per line, every request carrying its protocol version and client capabilities in _meta.

  • On upload, Subako sends server/discover, then tools/list until its last page. The package serves exactly the tools listed.
  • On a call, Subako sends one tools/call and closes stdin once it is answered. The module exits 0 when stdin ends.
  • A failure the model should read is a result with isError. A JSON-RPC error, a request from the module, a result that is not complete, or a stdout line that is not a message crashes the call.
  • Notifications are ignored, and a block Subako does not show is replaced by a line saying so.

To start a new package, use the template, which also packages the bundle Subako takes:

cargo generate subako-ai/subako-function-template-rust

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.