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
.wasmbinary 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.
| Permission | Allows | Risk Level |
|---|---|---|
log | Write to extension log | Low |
http:read | HTTP GET requests | Low |
http:write | HTTP POST/PUT/DELETE | Medium |
vault:read | Read stored secrets | Medium |
vault:write | Store secrets | Medium |
data:read | Read tasks, spaces, conversations | Low |
data:write | Create/modify tasks and data | Medium |
events:emit | Emit system events | Low |
events:subscribe | Listen to system events | Low |
filesystem:read | Read from sandboxed storage | Medium |
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:
| Limit | Default | Max | What happens |
|---|---|---|---|
| Fuel | 10M instructions | 10B | RuntimeError: fuel exhausted |
| Memory | 64 MB | 512 MB | RuntimeError: out of memory |
| Timeout | 30 seconds | 300 seconds | Execution cancelled |
| HTTP requests | 10 per execution | 100 | Request 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 binaryapplication/vnd.morphee.extension.manifest.v1+json— the manifestapplication/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 endpointsGET /describe— Extension manifestPOST /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
| Problem | Solution |
|---|---|
error[E0463]: can't find crate | Add morphee-sdk to Cargo.toml dependencies |
wasm32-wasip1 not found | Run rustup target add wasm32-wasip1 |
| Permission denied at runtime | Add the permission to manifest and re-approve during install |
| Fuel exhausted | Increase max_fuel in resource_limits or optimize your code |
| HTTP blocked | Check SSRF protection — localhost and metadata endpoints are blocked |