Skip to content

Creating Rust Plugins for WASImancer: A Step-by-Step Guide

๐Ÿšง work in progress

This guide will walk you through the process of creating, building, and running a WebAssembly plugin for WASImancer using Rust. We'll create a simple calculator plugin that can add two numbers.

Prerequisites

To follow this guide, you'll need:

Alternatively, you can use the Docker image provided by WASImancer:

docker pull k33g/wasm-builder:0.0.7

Project Setup

Let's create a new Rust library project:

mkdir -p addition
cd addition

Creating the Rust Project

Initialize a new Rust library project:

cargo init --lib

Configuring the Project for WebAssembly

First, edit the Cargo.toml file to set up your project for WebAssembly compilation:

[package]
name = "wasimancer-plugin-addition"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
extism-pdk = "1.3.0"
serde = { version = "1", features = ["derive"] }

Key points about this configuration: - crate-type = ["cdylib"] specifies that we're building a dynamic library suitable for WebAssembly - extism-pdk is the Extism Plugin Development Kit for Rust - serde is used for serializing and deserializing JSON data

Next, create a .cargo/config.toml file to set the default target:

[build]
target = "wasm32-unknown-unknown"

Writing the Plugin Code

Replace the contents of src/lib.rs with the following code:

use extism_pdk::*;
use serde::{Deserialize, Serialize};

// Define input structure for addition
#[derive(FromBytes, Deserialize, PartialEq, Debug)]
#[encoding(Json)]
struct Add {
    left: i32,
    right: i32,
}

// Define output structure for the sum
#[derive(ToBytes, Serialize, PartialEq, Debug)]
#[encoding(Json)]
struct Sum {
    value: i32,
}

#[plugin_fn]
pub fn add(input: Add) -> FnResult<Sum> {
    // Add the two numbers and return the result
    Ok(Sum {
        value: input.left + input.right,
    })
}

Understanding the Code

  1. We import the necessary components from extism_pdk and serde to handle WebAssembly interaction and JSON serialization.

  2. We define two structs:

  3. Add: Represents the input with two integers (left and right)
  4. Sum: Represents the output with a single integer (value)

  5. The #[plugin_fn] macro marks our add function as an exported plugin function.

  6. The add function takes an Add input, performs the addition, and returns a Sum result wrapped in FnResult.

  7. Both structs use the #[encoding(Json)] attribute to specify that they should be encoded/decoded as JSON.

Building the Plugin

Using Local Rust

To compile your Rust code to WebAssembly:

cargo build --release

The compiled WebAssembly file will be located at target/wasm32-unknown-unknown/release/wasimancer_plugin_addition.wasm.

Copy it to your project directory:

cp target/wasm32-unknown-unknown/release/wasimancer_plugin_addition.wasm ./

Using Docker

If you're using the WASImancer's Docker image:

1
2
3
4
5
6
7
8
docker run --rm -v "$PWD":/addition -w /addition k33g/wasm-builder:0.0.7 \
  bash -c "
    cargo clean && \
    cargo install cargo-cache && \
    cargo cache -a && \
    cargo build --release && \
    cp target/wasm32-unknown-unknown/release/wasimancer_plugin_addition.wasm ./
  "

Testing the Plugin Locally

You can test your plugin using the Extism CLI before deploying it to WASImancer:

Using Local Extism CLI

1
2
3
4
extism call wasimancer_plugin_addition.wasm add \
  --input '{"left":30, "right":12}' \
  --log-level "info" \
  --wasi

Using Docker

1
2
3
4
5
docker run --rm -v "$PWD":/addition -w /addition k33g/wasm-builder:0.0.7 \
  extism call wasimancer_plugin_addition.wasm add \
  --input '{"left":30, "right":12}' \
  --log-level "info" \
  --wasi

You should see output like: {"value":42}, indicating that the addition function correctly added 30 and 12.

Creating the Plugin Configuration

To use your plugin with WASImancer, you need to create a configuration in the plugins.yml file. Add the following entry:

plugins:
  - name: addition
    path: /addition/wasimancer_plugin_addition.wasm
    version: 1.0.0
    description: addition
    functions:
      - displayName: add with rust
        function: add
        arguments:
          - name: left
            type: number
            description: first number
          - name: right
            type: number
            description: second number
        description: a function to add numbers

Deploying the Plugin to WASImancer

Method 1: Manual Deployment

  1. Copy your WebAssembly file to the WASImancer plugins directory:

    cp wasimancer_plugin_addition.wasm /path/to/wasimancer/plugins/addition/
    

  2. Restart WASImancer or use the hot-reload API (next method).

Method 2: Using the Upload API

You can use the WASImancer API to upload your plugin without restarting the server:

  1. Create a shell script named publish.sh with the following content:
#!/bin/bash
TOKEN="wasimancer-rocks"  # Use your configured token

UPLOAD_ENDPOINT="http://localhost:3001/upload-plugin"
WASM_FILE="./wasimancer_plugin_addition.wasm"

read -r -d '' DATA <<- EOM
{
  "name": "addition",
  "path": "./bucket/wasimancer_plugin_addition.wasm",
  "version": "1.0.0",
  "description": "addition",
  "functions": [
    {
      "displayName": "add with rust",
      "function": "add",
      "arguments": [
        {
          "name": "left",
          "type": "number",
          "description": "first number"
        },
        {
          "name": "right",
          "type": "number",
          "description": "second number"
        }
      ],
      "description": "a function to add numbers"
    }
  ]
}
EOM

curl -X POST ${UPLOAD_ENDPOINT} \
  -H "Authorization: Bearer ${TOKEN}" \
  -F "wasmFile=@${WASM_FILE}" \
  -F "pluginData=${DATA}"
  1. Make the script executable and run it:
    chmod +x publish.sh
    ./publish.sh
    

Testing the Plugin in WASImancer

You can test your deployed plugin using the MCP Inspector:

  1. Start the Inspector:

    npx @modelcontextprotocol/inspector
    

  2. Connect to your WASImancer instance (typically at http://localhost:3001/sse).

  3. Navigate to the "Tools" tab, find your plugin, and click "Run Tool".

Debugging Tips for Rust Plugins

  1. Extism Logging: The Extism PDK provides logging functions:

    extism_pdk::log::info!("Processing calculation: {} {} {}", input.a, input.operation, input.b);
    

  2. Test with --log-level "debug" or --log-level "trace" flags: When using the Extism CLI, increase the log level for more details.

  3. Validate JSON Parsing: If you're having issues with JSON parsing, try adding explicit error handling:

    #[plugin_fn]
    pub fn calculate(json: String) -> FnResult<String> {
        // Manual parsing with explicit error handling
        let input: CalcInput = match serde_json::from_str(&json) {
            Ok(val) => val,
            Err(e) => return Err(Error::new(&format!("JSON parse error: {}", e))),
        };
    
        // Process as before...
    }
    

Best Practices for Rust Plugins

  1. Leverage Rust's Type System: Use Rust's strong type system to prevent bugs and make your code more maintainable.

  2. Use Result for Error Handling: Return meaningful errors rather than panicking, which can cause unexpected behavior in WebAssembly.

  3. Keep Dependencies Minimal: Large dependencies can increase your WebAssembly module size and slow down loading times.

  4. Optimize for Size: Use build flags to optimize for smaller WebAssembly modules:

    1
    2
    3
    [profile.release]
    lto = true
    opt-level = 's'
    

  5. Use Rust's Memory Management: Take advantage of Rust's ownership model to prevent memory leaks, which is especially important in long-running WebAssembly modules.

  6. Document Your Code: Add thorough documentation comments to make your plugins easier to maintain.

Build Script for Convenience

Create a build.sh script to simplify the build process:

1
2
3
4
5
#!/bin/bash
cargo clean
cargo build --release
cp target/wasm32-unknown-unknown/release/wasimancer_plugin_addition.wasm ./
ls -lh *.wasm

And a run.sh script to test your plugin:

1
2
3
4
5
6
#!/bin/bash
extism call wasimancer_plugin_addition.wasm add \
  --input '{"left":30, "right":12}' \
  --log-level "info" \
  --wasi
echo ""

Make them executable:

chmod +x build.sh run.sh

Conclusion

You've now learned how to create, build, test, and deploy a Rust plugin for WASImancer. Rust's combination of safety, performance, and WebAssembly support makes it an excellent choice for developing WASImancer plugins. By leveraging Rust's rich ecosystem and type system, you can create powerful, secure tools that enhance your AI applications through the Model Context Protocol.