Skip to main content

wasmcp_wasi/
lib.rs

1//! Minimal WASI SDK for MCP handlers
2//! 
3//! This provides just the essentials MCP handlers need:
4//! - Async HTTP client for outbound requests
5//! - Key-value storage (Spin only)
6//! - Configuration access
7
8pub mod http;
9pub mod config;
10
11#[cfg(feature = "keyvalue-draft2")]
12pub mod keyvalue {
13    //! Key-value storage using WASI KV interfaces (Spin only)
14    
15    use crate::wit::wasi::keyvalue0_2_0_draft2 as keyvalue;
16    use anyhow::Result;
17    
18    /// Response from listing keys
19    pub struct ListKeysResponse {
20        pub keys: Vec<String>,
21        pub cursor: Option<String>,
22    }
23    
24    /// A key-value store
25    pub struct Store {
26        bucket: keyvalue::store::Bucket,
27    }
28    
29    impl Store {
30        /// Open a store by name
31        pub fn open(name: &str) -> Result<Self> {
32            let bucket = keyvalue::store::open(name)?;
33            Ok(Self { bucket })
34        }
35        
36        /// Get a value by key
37        pub fn get(&self, key: &str) -> Result<Option<Vec<u8>>> {
38            match self.bucket.get(key) {
39                Ok(Some(value)) => Ok(Some(value)),
40                Ok(None) => Ok(None),
41                Err(e) => Err(anyhow::anyhow!("Failed to get key: {:?}", e)),
42            }
43        }
44        
45        /// Set a key-value pair
46        pub fn set(&self, key: &str, value: &[u8]) -> Result<()> {
47            self.bucket.set(key, value)?;
48            Ok(())
49        }
50        
51        /// Delete a key
52        pub fn delete(&self, key: &str) -> Result<()> {
53            self.bucket.delete(key)?;
54            Ok(())
55        }
56        
57        /// Check if a key exists
58        pub fn exists(&self, key: &str) -> Result<bool> {
59            self.bucket.exists(key)
60                .map_err(|e| anyhow::anyhow!("Failed to check key existence: {:?}", e))
61        }
62        
63        /// List all keys (Spin-compatible version)
64        pub fn list_keys(&self, cursor: Option<&str>) -> Result<ListKeysResponse> {
65            let response = self.bucket.list_keys(cursor)
66                .map_err(|e| anyhow::anyhow!("Failed to list keys: {:?}", e))?;
67            Ok(ListKeysResponse {
68                keys: response.keys,
69                cursor: response.cursor,
70            })
71        }
72    }
73}
74
75// Re-export spin_executor for blocking on async operations
76pub use spin_executor;
77
78// Generate WASI bindings
79#[doc(hidden)]
80pub mod wit {
81    #![allow(missing_docs)]
82    #![allow(warnings)]
83    
84    #[cfg(feature = "keyvalue-draft2")]
85    wit_bindgen::generate!({
86        world: "mcp-wasi-spin",
87        path: "./wit",
88        with: {
89            "wasi:io/error@0.2.0": ::wasi::io::error,
90            "wasi:io/streams@0.2.0": ::wasi::io::streams,
91            "wasi:io/poll@0.2.0": ::wasi::io::poll,
92        },
93        generate_all,
94    });
95    
96    #[cfg(not(feature = "keyvalue-draft2"))]
97    wit_bindgen::generate!({
98        world: "mcp-wasi",
99        path: "./wit",
100        with: {
101            "wasi:io/error@0.2.0": ::wasi::io::error,
102            "wasi:io/streams@0.2.0": ::wasi::io::streams,
103            "wasi:io/poll@0.2.0": ::wasi::io::poll,
104        },
105        generate_all,
106    });
107}