Write your own
Start a package from a template.
The quickstart deployed a package someone else wrote. This page starts one of your own from a template. Each template holds one working tool, the build script that packs the bundle Subako takes, and tests that run the tool as ordinary code.
Start from a template
You need Rust with the wasm32-wasip1 target, and
cargo-generate.
rustup target add wasm32-wasip1
cargo install cargo-generate
cargo generate subako-ai/subako-function-template-rust --name hello
cd hellosrc/main.rs holds one tool. Its doc comment is what the model reads, and
its input type is the tool’s schema:
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],
}cargo test
./package.sh # writes dist/hello.tar.gzYou need Node 20 or newer.
git clone https://github.com/subako-ai/subako-function-template-typescript hello
cd helloRename my-function to hello in package.json and
subako-package.json. src/index.ts holds one tool, with its schemas in
zod:
import { serve, tool } from "@subako-ai/function-sdk";
import { z } from "zod";
const add = tool({
name: "add",
description: "Add two numbers.",
input: z.object({
a: z.number().describe("the first number"),
b: z.number().describe("the second number"),
}),
output: z.object({ sum: z.number() }),
run: ({ a, b }) => ({ sum: a + b }),
});
serve([add]);npm install
npm run typecheck
npm run build # writes dist/
npx @subako-ai/function-sdk pack # writes hello.tar.gzYou need Go 1.25 or newer.
go run golang.org/x/tools/cmd/gonew@latest \
github.com/subako-ai/subako-function-template-go example.com/hello
cd hellomain.go is an MCP server written with the official MCP Go SDK. Each
mcp.AddTool is a tool, and its input struct is the tool’s schema:
type GreetInput struct {
Name string `json:"name" jsonschema:"who to greet"`
}
type Greeting struct {
Message string `json:"message"`
}
func greet(ctx context.Context, req *mcp.CallToolRequest, in GreetInput) (*mcp.CallToolResult, Greeting, error) {
if in.Name == "" {
return nil, Greeting{}, errors.New("name must not be empty")
}
return nil, Greeting{Message: "Hello, " + in.Name + "!"}, nil
}
func main() {
server := mcp.NewServer(&mcp.Implementation{Name: "hello", Version: "0.1.0"}, nil)
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "Greet someone by name."}, greet)
subakofn.Serve(server)
}go test ./...
bash package.sh # writes dist/hello.tar.gzThe result is a bundle: a .tar.gz holding subako-package.json, module.wasm, and any assets/ the module reads. Packages and versions describes it in full.
Try it on your machine
The Rust and Go templates’ tests call the tool as plain code — cargo test and go test — with no Subako involved, and npm run typecheck checks a TypeScript package against the globals its runtime provides. To run the built module itself the way Subako does, see Testing locally in the ABI.
Upload it, and every version after
Functions › Upload package takes the archive, as in the quickstart. Each later build goes up with Upload new version on the package’s page.
subako function create dist/hello.tar.gz # version 1
subako function push <function-id> dist/hello.tar.gz # every version afterEither command also takes the directory to pack, in place of the archive, so CI can push a version on every merge. See the CLI.
Then grant it to an agent under Function packages, as the quickstart does. An agent version that follows the package’s latest version picks up each push in the sessions started after it.