webmcp 0.1.2

Server-side WebMCP toolkit: validated tool definitions, script-safe manifests, shared browser runtime, Origin-Trial helpers, optional tower layer.
Documentation
use serde_json::Value;
use sha2::{Digest, Sha256};
use std::{fs, path::Path};
use webmcp::{Manifest, Tool, ToolDef, Transport, RUNTIME_JS, RUNTIME_SHA256};

fn fixture(name: &str) {
    let path = Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("conformance/fixtures")
        .join(name);
    let fixture: Value = serde_json::from_slice(&fs::read(path).unwrap()).unwrap();
    let definition: ToolDef = serde_json::from_value(fixture["definition"].clone()).unwrap();
    let transport: Transport = serde_json::from_value(fixture["transport"].clone()).unwrap();
    let tool = Tool::new(definition).unwrap();
    let manifest = Manifest::build(&[&tool], transport).unwrap();
    let parsed: Value = serde_json::from_str(&manifest.to_json()).unwrap();
    assert_eq!(parsed["webmcpManifestVersion"], 1);
    assert_eq!(parsed["transport"], fixture["transport"]);
    assert_eq!(parsed["tools"].as_array().unwrap().len(), 1);
    assert_eq!(parsed["tools"][0], fixture["expected"], "fixture {name}");
}

#[test]
fn read_array_fixture() {
    fixture("read-array.json");
}
#[test]
fn write_post_fixture() {
    fixture("write-post.json");
}
#[test]
fn projection_result_fixture() {
    fixture("projection-result.json");
}
// Every shared fixture, so a fixture added in the Ruby reference is covered here too.
#[test]
fn every_shared_fixture() {
    let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("conformance/fixtures");
    let mut names: Vec<String> = fs::read_dir(dir)
        .unwrap()
        .map(|entry| entry.unwrap().file_name().into_string().unwrap())
        .filter(|name| name.ends_with(".json"))
        .collect();
    names.sort();
    assert!(
        names.len() >= 4,
        "expected the shared fixtures, found {names:?}"
    );
    for name in names {
        fixture(&name);
    }
}

#[test]
fn runtime_matches_pinned_sha256() {
    let root = Path::new(env!("CARGO_MANIFEST_DIR"));
    let pin = fs::read_to_string(root.join("conformance/RUNTIME.sha256")).unwrap();
    let fields: Vec<_> = pin.split_whitespace().collect();
    assert_eq!(fields.len(), 2);
    assert_eq!(fields[1], "runtime/webmcp-runtime.js");
    let bytes = fs::read(root.join(fields[1])).unwrap();
    assert_eq!(bytes, RUNTIME_JS.as_bytes());
    assert_eq!(format!("{:x}", Sha256::digest(&bytes)), fields[0]);
    assert_eq!(RUNTIME_SHA256, fields[0]);
}