# subako-function-sdk
Write [Subako](https://subako.ai) 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.
```rust
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.
```rust
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.
```rust
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:
```sh
cargo generate subako-ai/subako-function-template-rust
```
## License
Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or [MIT license](LICENSE-MIT) at your option.