Server wasm compatibility (#15)
* add axum sse example * fix: make the sse axum folder name correct * feat: add common modules for Axum SSE example This commit adds supporting modules for the Axum Server-Sent Events (SSE) example: - Added `counter.rs` with a simple counter router implementation - Added `jsonrpc_frame_codec.rs` for decoding JSON-RPC frames - Created a `mod.rs` to expose these modules - Removed the previous SSE example configuration from Cargo.toml * refactor: remove main.rs and update Cargo.toml for WASM compatibility This commit makes two key changes: 1. Removes the main.rs file and make it a example 2. Add a wasi_std_io example * refactor: simplify WASI I/O handling in example and fmt Merged AsyncInputStream and AsyncOutputStream into a single WasiFd struct with std_in() and std_out() methods, reducing code duplication and improving clarity of the WASI standard I/O example. * move examples to root
This commit is contained in:
parent
c0bd94dd85
commit
4597d1f047
3 changed files with 141 additions and 4 deletions
|
|
@ -14,7 +14,7 @@ mcp-macros = { workspace = true }
|
|||
serde = { version = "1.0.216", features = ["derive"] }
|
||||
serde_json = "1.0.133"
|
||||
schemars = "0.8"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio = { version = "1", features = ["io-util"] }
|
||||
tower = { version = "0.4", features = ["timeout"] }
|
||||
tower-service = "0.3"
|
||||
futures = "0.3"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ publish = false
|
|||
mcp-server = { path = "../../crates/mcp-server" }
|
||||
mcp-core = { path = "../../crates/mcp-core" }
|
||||
mcp-macros = { path = "../../crates/mcp-macros" }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio = { version = "1", features = ["io-util"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
anyhow = "1.0"
|
||||
|
|
@ -18,14 +18,26 @@ tracing-appender = "0.2"
|
|||
futures = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
axum = { version = "0.8", features = ["macros"] }
|
||||
|
||||
tokio-util = { version = "0.7", features = ["io", "codec"]}
|
||||
rand = { version = "0.8" }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
||||
axum = { version = "0.8", features = ["macros"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||
tokio = { version = "1", features = ["io-util", "rt", "time", "macros"] }
|
||||
wasi = { version = "0.11.0+wasi-snapshot-preview1" }
|
||||
|
||||
[[example]]
|
||||
name = "counter-server"
|
||||
path = "src/counter_server.rs"
|
||||
|
||||
[[example]]
|
||||
name = "axum"
|
||||
path = "src/axum.rs"
|
||||
path = "src/axum.rs"
|
||||
|
||||
[[example]]
|
||||
name = "wasi_std_io"
|
||||
path = "src/wasi_std_io.rs"
|
||||
125
examples/servers/src/wasi_std_io.rs
Normal file
125
examples/servers/src/wasi_std_io.rs
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
//! build: cargo build -p mcp-server-examples --example wasi_std_io --target wasm32-wasip1
|
||||
//!
|
||||
//! run: npx @modelcontextprotocol/inspector wasmedge --dir logs:. run target/wasm32-wasip1/debug/examples/wasi_std_io.wasm
|
||||
//!
|
||||
use mcp_server::{router::RouterService, ByteTransport, Server};
|
||||
use tracing_appender::rolling::{RollingFileAppender, Rotation};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
mod common;
|
||||
use anyhow::Result;
|
||||
use common::counter::CounterRouter;
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<()> {
|
||||
// Set up file appender for logging
|
||||
let file_appender = RollingFileAppender::new(Rotation::DAILY, "logs", "mcp-server.log");
|
||||
|
||||
// Initialize the tracing subscriber with file and stdout logging
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(EnvFilter::from_default_env().add_directive(tracing::Level::INFO.into()))
|
||||
.with_writer(file_appender)
|
||||
.with_target(false)
|
||||
.with_thread_ids(true)
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.init();
|
||||
|
||||
tracing::info!("Starting MCP server");
|
||||
|
||||
// Create an instance of our counter router
|
||||
let router = RouterService(CounterRouter::new());
|
||||
|
||||
// Create and run the server
|
||||
let server = Server::new(router);
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let transport = ByteTransport::new(async_io::WasiFd::std_in(), async_io::WasiFd::std_out());
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let transport = ByteTransport::new(tokio::io::stdin(), tokio::io::stdout());
|
||||
|
||||
tracing::info!("Server initialized and ready to handle requests");
|
||||
Ok(server.run(transport).await?)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod async_io {
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use wasi::{Fd, FD_STDIN, FD_STDOUT};
|
||||
|
||||
pub struct WasiFd {
|
||||
fd: Fd,
|
||||
}
|
||||
|
||||
impl WasiFd {
|
||||
pub fn std_in() -> Self {
|
||||
Self { fd: FD_STDIN }
|
||||
}
|
||||
pub fn std_out() -> Self {
|
||||
Self { fd: FD_STDOUT }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for WasiFd {
|
||||
fn poll_read(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
buf: &mut tokio::io::ReadBuf<'_>,
|
||||
) -> std::task::Poll<std::io::Result<()>> {
|
||||
let mut temp_buf = vec![0u8; buf.remaining()];
|
||||
unsafe {
|
||||
match wasi::fd_read(
|
||||
self.fd,
|
||||
&[wasi::Iovec {
|
||||
buf: temp_buf.as_mut_ptr(),
|
||||
buf_len: temp_buf.len(),
|
||||
}],
|
||||
) {
|
||||
Ok(n) => {
|
||||
buf.put_slice(&temp_buf[..n]);
|
||||
std::task::Poll::Ready(Ok(()))
|
||||
}
|
||||
Err(err) => std::task::Poll::Ready(Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("WASI read error: {}", err),
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for WasiFd {
|
||||
fn poll_write(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> std::task::Poll<Result<usize, std::io::Error>> {
|
||||
unsafe {
|
||||
match wasi::fd_write(
|
||||
self.fd,
|
||||
&[wasi::Ciovec {
|
||||
buf: buf.as_ptr(),
|
||||
buf_len: buf.len(),
|
||||
}],
|
||||
) {
|
||||
Ok(n) => std::task::Poll::Ready(Ok(n)),
|
||||
Err(err) => std::task::Poll::Ready(Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("WASI write error: {}", err),
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||
std::task::Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||
self.poll_flush(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue