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 = { version = "1.0.216", features = ["derive"] }
|
||||||
serde_json = "1.0.133"
|
serde_json = "1.0.133"
|
||||||
schemars = "0.8"
|
schemars = "0.8"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["io-util"] }
|
||||||
tower = { version = "0.4", features = ["timeout"] }
|
tower = { version = "0.4", features = ["timeout"] }
|
||||||
tower-service = "0.3"
|
tower-service = "0.3"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ publish = false
|
||||||
mcp-server = { path = "../../crates/mcp-server" }
|
mcp-server = { path = "../../crates/mcp-server" }
|
||||||
mcp-core = { path = "../../crates/mcp-core" }
|
mcp-core = { path = "../../crates/mcp-core" }
|
||||||
mcp-macros = { path = "../../crates/mcp-macros" }
|
mcp-macros = { path = "../../crates/mcp-macros" }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["io-util"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
|
@ -18,14 +18,26 @@ tracing-appender = "0.2"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
axum = { version = "0.8", features = ["macros"] }
|
|
||||||
tokio-util = { version = "0.7", features = ["io", "codec"]}
|
tokio-util = { version = "0.7", features = ["io", "codec"]}
|
||||||
rand = { version = "0.8" }
|
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]]
|
[[example]]
|
||||||
name = "counter-server"
|
name = "counter-server"
|
||||||
path = "src/counter_server.rs"
|
path = "src/counter_server.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "axum"
|
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