Skip to content

Commit 4597d1f

Browse files
authored
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
1 parent c0bd94d commit 4597d1f

File tree

3 files changed

+141
-4
lines changed

3 files changed

+141
-4
lines changed

crates/mcp-server/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ mcp-macros = { workspace = true }
1414
serde = { version = "1.0.216", features = ["derive"] }
1515
serde_json = "1.0.133"
1616
schemars = "0.8"
17-
tokio = { version = "1", features = ["full"] }
17+
tokio = { version = "1", features = ["io-util"] }
1818
tower = { version = "0.4", features = ["timeout"] }
1919
tower-service = "0.3"
2020
futures = "0.3"

examples/servers/Cargo.toml

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ publish = false
88
mcp-server = { path = "../../crates/mcp-server" }
99
mcp-core = { path = "../../crates/mcp-core" }
1010
mcp-macros = { path = "../../crates/mcp-macros" }
11-
tokio = { version = "1", features = ["full"] }
11+
tokio = { version = "1", features = ["io-util"] }
1212
serde = { version = "1.0", features = ["derive"] }
1313
serde_json = "1.0"
1414
anyhow = "1.0"
@@ -18,14 +18,26 @@ tracing-appender = "0.2"
1818
futures = "0.3"
1919

2020
[dev-dependencies]
21-
axum = { version = "0.8", features = ["macros"] }
21+
2222
tokio-util = { version = "0.7", features = ["io", "codec"]}
2323
rand = { version = "0.8" }
2424

25+
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
26+
axum = { version = "0.8", features = ["macros"] }
27+
tokio = { version = "1", features = ["full"] }
28+
29+
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
30+
tokio = { version = "1", features = ["io-util", "rt", "time", "macros"] }
31+
wasi = { version = "0.11.0+wasi-snapshot-preview1" }
32+
2533
[[example]]
2634
name = "counter-server"
2735
path = "src/counter_server.rs"
2836

2937
[[example]]
3038
name = "axum"
31-
path = "src/axum.rs"
39+
path = "src/axum.rs"
40+
41+
[[example]]
42+
name = "wasi_std_io"
43+
path = "src/wasi_std_io.rs"

examples/servers/src/wasi_std_io.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//! build: cargo build -p mcp-server-examples --example wasi_std_io --target wasm32-wasip1
2+
//!
3+
//! run: npx @modelcontextprotocol/inspector wasmedge --dir logs:. run target/wasm32-wasip1/debug/examples/wasi_std_io.wasm
4+
//!
5+
use mcp_server::{router::RouterService, ByteTransport, Server};
6+
use tracing_appender::rolling::{RollingFileAppender, Rotation};
7+
use tracing_subscriber::EnvFilter;
8+
mod common;
9+
use anyhow::Result;
10+
use common::counter::CounterRouter;
11+
#[tokio::main(flavor = "current_thread")]
12+
async fn main() -> Result<()> {
13+
// Set up file appender for logging
14+
let file_appender = RollingFileAppender::new(Rotation::DAILY, "logs", "mcp-server.log");
15+
16+
// Initialize the tracing subscriber with file and stdout logging
17+
tracing_subscriber::fmt()
18+
.with_env_filter(EnvFilter::from_default_env().add_directive(tracing::Level::INFO.into()))
19+
.with_writer(file_appender)
20+
.with_target(false)
21+
.with_thread_ids(true)
22+
.with_file(true)
23+
.with_line_number(true)
24+
.init();
25+
26+
tracing::info!("Starting MCP server");
27+
28+
// Create an instance of our counter router
29+
let router = RouterService(CounterRouter::new());
30+
31+
// Create and run the server
32+
let server = Server::new(router);
33+
#[cfg(target_arch = "wasm32")]
34+
let transport = ByteTransport::new(async_io::WasiFd::std_in(), async_io::WasiFd::std_out());
35+
#[cfg(not(target_arch = "wasm32"))]
36+
let transport = ByteTransport::new(tokio::io::stdin(), tokio::io::stdout());
37+
38+
tracing::info!("Server initialized and ready to handle requests");
39+
Ok(server.run(transport).await?)
40+
}
41+
42+
#[cfg(target_arch = "wasm32")]
43+
mod async_io {
44+
use tokio::io::{AsyncRead, AsyncWrite};
45+
use wasi::{Fd, FD_STDIN, FD_STDOUT};
46+
47+
pub struct WasiFd {
48+
fd: Fd,
49+
}
50+
51+
impl WasiFd {
52+
pub fn std_in() -> Self {
53+
Self { fd: FD_STDIN }
54+
}
55+
pub fn std_out() -> Self {
56+
Self { fd: FD_STDOUT }
57+
}
58+
}
59+
60+
impl AsyncRead for WasiFd {
61+
fn poll_read(
62+
self: std::pin::Pin<&mut Self>,
63+
_cx: &mut std::task::Context<'_>,
64+
buf: &mut tokio::io::ReadBuf<'_>,
65+
) -> std::task::Poll<std::io::Result<()>> {
66+
let mut temp_buf = vec![0u8; buf.remaining()];
67+
unsafe {
68+
match wasi::fd_read(
69+
self.fd,
70+
&[wasi::Iovec {
71+
buf: temp_buf.as_mut_ptr(),
72+
buf_len: temp_buf.len(),
73+
}],
74+
) {
75+
Ok(n) => {
76+
buf.put_slice(&temp_buf[..n]);
77+
std::task::Poll::Ready(Ok(()))
78+
}
79+
Err(err) => std::task::Poll::Ready(Err(std::io::Error::new(
80+
std::io::ErrorKind::Other,
81+
format!("WASI read error: {}", err),
82+
))),
83+
}
84+
}
85+
}
86+
}
87+
88+
impl AsyncWrite for WasiFd {
89+
fn poll_write(
90+
self: std::pin::Pin<&mut Self>,
91+
_cx: &mut std::task::Context<'_>,
92+
buf: &[u8],
93+
) -> std::task::Poll<Result<usize, std::io::Error>> {
94+
unsafe {
95+
match wasi::fd_write(
96+
self.fd,
97+
&[wasi::Ciovec {
98+
buf: buf.as_ptr(),
99+
buf_len: buf.len(),
100+
}],
101+
) {
102+
Ok(n) => std::task::Poll::Ready(Ok(n)),
103+
Err(err) => std::task::Poll::Ready(Err(std::io::Error::new(
104+
std::io::ErrorKind::Other,
105+
format!("WASI write error: {}", err),
106+
))),
107+
}
108+
}
109+
}
110+
111+
fn poll_flush(
112+
self: std::pin::Pin<&mut Self>,
113+
_cx: &mut std::task::Context<'_>,
114+
) -> std::task::Poll<Result<(), std::io::Error>> {
115+
std::task::Poll::Ready(Ok(()))
116+
}
117+
118+
fn poll_shutdown(
119+
self: std::pin::Pin<&mut Self>,
120+
cx: &mut std::task::Context<'_>,
121+
) -> std::task::Poll<Result<(), std::io::Error>> {
122+
self.poll_flush(cx)
123+
}
124+
}
125+
}

0 commit comments

Comments
 (0)