Skip to main content

Extension Developer Guide

Build WASM extensions for Morphee. Extensions add new capabilities — integrations, tools, custom actions — that run in a sandboxed environment on all platforms.

Architecture Overview

Developer writes Rust → compiles to .wasm → publishes to OCI registry

User installs extension → PermissionDialog → WASM loaded in runtime

AI chat invokes action → Runtime executes WASM → host functions → result

Key properties:

  • Extensions are sandboxed WASM modules with explicit permissions
  • Same .wasm binary runs on Python backend (wasmtime) and Tauri desktop/mobile (wasmer)
  • Resource limits: fuel (CPU), memory, HTTP rate, execution timeout
  • Code signing with RSA-PSS + SHA-256

Getting Started

Prerequisites

  • Rust toolchain (rustup)
  • WASM target: rustup target add wasm32-wasip1
  • Python 3.10+ (for CLI tool)

Create an Extension

# Install the CLI
pip install -e sdk/morphee-ext-cli

# Create from template
morphee-ext init my-extension
cd my-extension

This creates:

my-extension/
├── Cargo.toml # Rust project config
├── manifest.json # Extension metadata
└── src/
└── lib.rs # Extension implementation

Implement the Extension Trait

use morphee_sdk::{Extension, Request, Response};

#[derive(Default)]
struct MyExtension;

impl Extension for MyExtension {
fn describe(&self) -> &str {
// Return manifest as JSON string
r#"{"id":"my-ext","name":"My Extension","version":"0.1.0",
"permissions":["log"],
"actions":[{"name":"greet","description":"Say hello"}]}"#
}

fn execute(&self, request: &Request) -> Response {
match request.action.as_str() {
"greet" => {
let name = request.params.get("name")
.and_then(|v| v.as_str())
.unwrap_or("World");
Response::success(serde_json::json!({
"message": format!("Hello, {}!", name)
}))
}
_ => Response::error(format!("Unknown: {}", request.action)),
}
}
}

morphee_sdk::export_extension!(MyExtension);

Build

cargo build --target wasm32-wasip1 --release
# Output: target/wasm32-wasip1/release/my_extension.wasm

Test Locally

# Start dev server with hot reload
morphee-ext dev

# In another terminal:
curl -X POST http://localhost:9090/execute \
-H "Content-Type: application/json" \
-d '{"action": "greet", "params": {"name": "Alice"}}'

The dev server watches for .wasm changes and reloads automatically.

Manifest Format

Every extension has a manifest (returned by describe()):

{
"id": "com.company.extension-name",
"name": "Human-Readable Name",
"version": "1.0.0",
"description": "What this extension does",
"author": "Author Name",
"permissions": ["http:read", "vault:read", "log"],
"actions": [
{
"name": "action_name",
"description": "What this action does",
"parameters": [
{
"name": "param_name",
"type": "string",
"description": "Parameter description",
"required": true
}
]
}
],
"resource_limits": {
"max_memory_mb": 64,
"max_fuel": 10000000,
"max_execution_time_seconds": 30,
"max_http_requests": 10
}
}

Permissions

Extensions must declare permissions upfront. Users approve them during install.

PermissionAllowsRisk Level
logWrite to extension logLow
http:readHTTP GET requestsLow
http:writeHTTP POST/PUT/DELETEMedium
vault:readRead stored secretsMedium
vault:writeStore secretsMedium
data:readRead tasks, spaces, conversationsLow
data:writeCreate/modify tasks and dataMedium
events:emitEmit system eventsLow
events:subscribeListen to system eventsLow
filesystem:readRead from sandboxed storageMedium

Security notes:

  • HTTP requests are SSRF-protected (localhost, metadata endpoints blocked)
  • Domain allowlists can restrict which URLs an extension accesses
  • Vault secrets are per-extension (isolated from other extensions)
  • File access is sandboxed per group

Host Functions

Extensions call host functions to interact with Morphee. These are available at runtime (not in local dev mock mode):

HTTP

// In your execute() method:
// host::http_fetch("GET", "https://api.example.com/data", headers, body)
// Returns: {status, headers, body}

Vault (Secrets)

// host::vault_get("api_key") → Option<String>
// host::vault_set("api_key", "sk-...") → bool

Events

// host::event_emit("task.created", json_payload) → bool

Data

// host::data_get_task(task_id) → Task JSON
// host::data_create_task(title, description) → Task JSON

Logging

// host::log("info", "Processing request...")

Resource Limits

Extensions run with bounded resources:

LimitDefaultMaxWhat happens
Fuel10M instructions10BRuntimeError: fuel exhausted
Memory64 MB512 MBRuntimeError: out of memory
Timeout30 seconds300 secondsExecution cancelled
HTTP requests10 per execution100Request denied

Custom limits can be set in the manifest's resource_limits field.

Code Signing

Production extensions must be signed:

# Generate key pair (one-time)
openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem

# Sign extension
morphee-ext sign --key private_key.pem

The signature covers both the WASM binary and manifest (canonical JSON). The runtime verifies signatures before loading in production mode.

Publishing

# Build release
cargo build --target wasm32-wasip1 --release

# Sign
morphee-ext sign --key private_key.pem

# Publish to registry
morphee-ext publish --registry https://registry.morphee.app

Extensions are distributed as OCI artifacts with three layers:

  • application/vnd.morphee.extension.wasm.v1 — the WASM binary
  • application/vnd.morphee.extension.manifest.v1+json — the manifest
  • application/vnd.morphee.extension.signature.v1+json — the signature

Examples

The SDK includes three example extensions:

1. Echo (Minimal)

sdk/morphee-sdk-rust/examples/echo.rs — One action, no external dependencies.

2. Hello API (HTTP)

sdk/morphee-sdk-rust/examples/hello_api.rs — Demonstrates HTTP fetch with http:read permission.

3. Task Creator (Data)

sdk/morphee-sdk-rust/examples/task_creator.rs — Creates/lists/completes tasks via data:read + data:write.

Debugging

Dev Server

The morphee-ext dev command starts a local HTTP server with hot reload:

  • GET / — Extension info and available endpoints
  • GET /describe — Extension manifest
  • POST /execute — Execute an action

Logs

Extension logs (via host::log()) appear in the backend logs and the audit trail:

GET /api/extensions/{id}/audit

Common Issues

ProblemSolution
error[E0463]: can't find crateAdd morphee-sdk to Cargo.toml dependencies
wasm32-wasip1 not foundRun rustup target add wasm32-wasip1
Permission denied at runtimeAdd the permission to manifest and re-approve during install
Fuel exhaustedIncrease max_fuel in resource_limits or optimize your code
HTTP blockedCheck SSRF protection — localhost and metadata endpoints are blocked