Wassette Documentation

repository·main·Indexed 21 days ago

https://github.com/microsoft/wassette

A security-oriented runtime for running WebAssembly Components as tools for AI agents via the Model Context Protocol (MCP). It utilizes the Wasmtime sandbox for isolated security and includes the component2json library for converting WebAssembly Interface Types (WIT) to JSON Schema, as well as a capability-based security policy framework for managing storage, network, environment, and resource permissions.

Tokens
87.6K
Snippets
312
Records
407
Agent score
74%

What's inside Wassette

  1. What is Wassette?

    main

    Wassette is a secure, open-source Model Context Protocol (MCP) server that uses WebAssembly (Wasm) to provide a trusted execution environment for untrusted tools. It allows LLMs to access external tools safely by embedding a WebAssembly runtime and applying fine-grained security policies.

    Instead of running MCP servers as standalone processes with host-level privileges, Wassette runs tools as sandboxed WebAssembly components, providing isolation for file system, network, and system resources.

  2. Use component2json for WebAssembly Component conversions

    main

    The component2json library provides tools to convert WebAssembly Components into JSON Schema and manage conversions between JSON and WebAssembly Interface Type (WIT) values.

    Key capabilities include:

    • Generating JSON schemas for all exported functions in a component.
    • Converting JSON objects into WIT Val arguments based on expected types.
    • Converting WIT values back into JSON format.
    • Creating placeholder results for function return values.
    use component2json::{component_exports_to_json_schema, json_to_vals, vals_to_json, create_placeholder_results};
    use wasmtime::component::{Component, Type, Val};
    use wasmtime::Engine;
    
    // 1. Setup Wasmtime Engine with component model enabled
    let mut config = wasmtime::Config::new();
    config.wasm_component_model(true);
    let engine = Engine::new(&config)?;
    
    // 2. Load the component
    let component_wat = r#"(component)"#;
    let component = Component::new(&engine, component_wat)?;
    
    // 3. Get JSON schema for all exported functions
    let schema = component_exports_to_json_schema(&component, &engine, true);
    
    // 4. Convert JSON to WIT values
    let func_param_types = vec![
        ("name".to_string(), Type::String),
        ("value".to_string(), Type::U32),
    ];
    let json_args = serde_json::json!({
        "name": "example",
        "value": 42
    });
    let wit_vals = json_to_vals(&json_args, &func_param_types)?;
    
    // 5. Convert WIT values back to JSON
    let json_result = vals_to_json(&wit_vals);
    
    // 6. Create placeholder results
    let result_types = vec![Type::String, Type::U32];
    let placeholder_results = create_placeholder_results(&result_types);
  3. Authoring Wasm Components with Python

    main
    Wassette allows you to run Python tools as secure, isolated WebAssembly (Wasm) components via the Model Context Protocol (MCP). These components are built using componentize-py, follow the WebAssembly Component Model, and use WIT (WebAssembly Interface Types) for interface definitions. They run in a sandboxed environment with policy-controlled capabilities.
  4. What is Wassette and how does it differ from traditional MCP servers?

    main

    Wassette is a local Model Context Protocol (MCP) server that uses WebAssembly (Wasm) to provide a secure, sandboxed execution environment for MCP tools.

    Unlike traditional MCP servers that run with the same privileges as the host process, Wassette provides:

    • Sandboxed execution: Tools run in a Wasm sandbox, not directly on the host.
    • Fine-grained permissions: Explicit control over filesystem, network, and system resources.
    • Component-based architecture: Uses the WebAssembly Component Model for interoperability.
    • Centralized security: A single trusted computing base for all tools.
  5. What is WIT (WebAssembly Interface Types)?

    main

    WIT is an Interface Definition Language (IDL) used to define how your Wasm component interacts with Wassette and other systems. All language guides for Wassette involve writing WIT interfaces to declare component capabilities.

    Example WIT interface definition:

    package local:my-tool;
    
    world my-component {
        export process: func(input: string) -> result<string, string>;
    }
  6. Reducing Supply Chain Attack Risks

    main

    Supply chain attacks target the distribution of components (e.g., malicious code injected into a trusted component).

    Wassette's Mitigation Strategy:

    • Wasm Sandboxing: Provides a strong isolation boundary; even a malicious component is confined to the Wasm runtime and its granted permissions.
    • Explicit Authorization: All external interactions (network/file system) require explicit permission, allowing users to audit policies before loading components.
    • Tiered Trust Sources: Wassette supports loading components from different sources with varying trust levels:
      • Local file systems: Highest trust.
      • OCI registries: Medium trust (uses content addressing).
      • HTTPS URLs: Lowest trust.
    • Immutability: Components are immutable once loaded, preventing runtime tampering.
  7. Understand the Structured Result Wrapper

    main

    All function return values produced by component_exports_to_json_schema and vals_to_json are wrapped in a JSON object with a required result property. This ensures a consistent access pattern for downstream consumers.

    • Single return values: { "result": VALUE }
    • Multiple return values: { "result": { "val0": VALUE0, "val1": VALUE1, ... } }
  8. Understand Wassette architecture and security

    main

    For deep technical understanding of how Wassette operates, consult the Design & Architecture documentation:

    • Architecture: The high-level system design.
    • Permission System: The internal logic governing component access.
    • MCP Threat Model: Security analysis of the Model Context Protocol implementation.
    • Component Schemas & Structured Output: How component2json handles data formats.
    • Agentic Workflows: How Wassette integrates into autonomous agent loops.
  9. Understand the Release Branch Strategy

    main

    Wassette uses a release branch strategy to ensure that the release process does not block active development on the main branch:

    1. Branching: CHANGELOG updates and release preparations are performed on a dedicated release/vX.Y.Z branch.
    2. Automation: The .github/workflows/release.yml workflow uses changelog_utils.py to extract notes for GitHub releases and update the CHANGELOG.md file (moving [Unreleased] content to the new version).
    3. Merging: A Pull Request is automatically created to merge the release/vX.Y.Z branch back into main once the release is prepared.
    4. Continuity: This allows developers to continue working on main without interruption while the release is being finalized.
  10. Working with Complex Data Types (Records, Variants, Lists)

    main

    WIT supports rich data types like record (structs), variant (enums/unions), and list. When using these in Python, they are mapped to classes and types in the wit_world package.

    WIT Definition Example:

    world advanced-tool {
        export process-user: func(user: user-info) -> result<string, string>;
        export handle-event: func(event: app-event) -> result<string, string>;
        export process-batch: func(items: list<string>) -> result<list<string>, string>;
    }
    
    record user-info {
        name: string,
        age: u32,
        email: string,
    }
    
    variant app-event {
        user-login(user-info),
        user-logout(string),
        data-update(string),
    }

    Python Implementation Example:

    from wit_world.types import UserInfo, AppEvent
    
    class AdvancedTool(wit_world.AdvancedTool):
        def process_user(self, user: UserInfo) -> str:
            return json.dumps({
                "processed": True,
                "user": {
                    "name": user.name,
                    "age": user.age,
                    "email": user.email
                }
            })
        
        def handle_event(self, event: AppEvent) -> str:
            if isinstance(event, AppEvent.UserLogin):
                return f"User {event.value.name} logged in"
            elif isinstance(event, AppEvent.UserLogout):
                return f"User {event.value} logged out"
            # ... handle other variants
        
        def process_batch(self, items: list[str]) -> list[str]:
            return [item.upper() for item in items]
  11. Handle optional parameters in function calls

    main

    When calling functions that accept optional parameters, use the literal none for missing values or some("value") to provide a value.

    # No optional value
    --invoke 'list-branches("owner", "repo", none, none)'
    
    # With optional value
    --invoke 'create-branch("owner", "repo", "new-branch", some("main"))'