Skip to content

Commit ecec2d0

Browse files
authored
Merge pull request #51 from guyernest/feature/server-refactoring-wasi-target
fix: Align mcp-tester with WASM MCP server implementations
2 parents d1cf3db + 5691fe0 commit ecec2d0

File tree

14 files changed

+1253
-31
lines changed

14 files changed

+1253
-31
lines changed

examples/26-server-tester/src/main.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ async fn main() -> Result<()> {
215215
cli.insecure,
216216
cli.api_key.as_deref(),
217217
cli.transport.as_deref(),
218+
cli.verbose > 0,
218219
)
219220
.await
220221
},
@@ -409,6 +410,7 @@ async fn run_tools_test(
409410
insecure: bool,
410411
api_key: Option<&str>,
411412
transport: Option<&str>,
413+
verbose: bool,
412414
) -> Result<TestReport> {
413415
let mut tester = ServerTester::new(
414416
url,
@@ -421,7 +423,10 @@ async fn run_tools_test(
421423
println!("{}", "Discovering and testing tools...".green());
422424
println!();
423425

424-
tester.run_tools_discovery(test_all).await
426+
// Pass verbose flag to the tester for detailed output
427+
tester
428+
.run_tools_discovery_with_verbose(test_all, verbose)
429+
.await
425430
}
426431

427432
async fn run_diagnostics(

examples/26-server-tester/src/scenario_executor.rs

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -237,12 +237,62 @@ impl<'a> ScenarioExecutor<'a> {
237237

238238
match operation {
239239
Operation::ToolCall { tool, arguments } => {
240-
let result = self.tester.test_tool(&tool, arguments).await?;
241-
Ok(json!({
242-
"success": result.status == crate::report::TestStatus::Passed,
243-
"result": result.details,
244-
"error": result.error
245-
}))
240+
// Call the tool directly to get raw response for assertions
241+
match self.tester.transport_type {
242+
crate::tester::TransportType::Http => {
243+
if let Some(ref client) = self.tester.pmcp_client {
244+
match client.call_tool(tool.clone(), arguments).await {
245+
Ok(result) => {
246+
// Extract the text content from the response
247+
let content_text = result
248+
.content
249+
.into_iter()
250+
.filter_map(|c| match c {
251+
pmcp::types::Content::Text { text } => Some(text),
252+
_ => None,
253+
})
254+
.collect::<Vec<_>>()
255+
.join("\n");
256+
257+
// Check if the content indicates an error
258+
if content_text.starts_with("Error:") {
259+
Ok(json!({
260+
"success": false,
261+
"result": null,
262+
"error": content_text
263+
}))
264+
} else {
265+
Ok(json!({
266+
"success": true,
267+
"result": content_text,
268+
"error": null
269+
}))
270+
}
271+
},
272+
Err(e) => Ok(json!({
273+
"success": false,
274+
"result": null,
275+
"error": e.to_string()
276+
})),
277+
}
278+
} else {
279+
Ok(json!({
280+
"success": false,
281+
"result": null,
282+
"error": "Client not initialized"
283+
}))
284+
}
285+
},
286+
_ => {
287+
// Fall back to test_tool for other transport types
288+
let result = self.tester.test_tool(&tool, arguments).await?;
289+
Ok(json!({
290+
"success": result.status == crate::report::TestStatus::Passed,
291+
"result": result.details,
292+
"error": result.error
293+
}))
294+
},
295+
}
246296
},
247297

248298
Operation::ListTools => {
@@ -523,7 +573,10 @@ impl<'a> ScenarioExecutor<'a> {
523573
},
524574

525575
Assertion::Success => {
526-
let has_error = response.get("error").is_some();
576+
let has_error = response
577+
.get("error")
578+
.and_then(|e| if e.is_null() { None } else { Some(e) })
579+
.is_some();
527580
AssertionResult {
528581
assertion: "Success".to_string(),
529582
passed: !has_error,
@@ -538,7 +591,10 @@ impl<'a> ScenarioExecutor<'a> {
538591
},
539592

540593
Assertion::Failure => {
541-
let has_error = response.get("error").is_some();
594+
let has_error = response
595+
.get("error")
596+
.and_then(|e| if e.is_null() { None } else { Some(e) })
597+
.is_some();
542598
AssertionResult {
543599
assertion: "Failure".to_string(),
544600
passed: has_error,

examples/26-server-tester/src/tester.rs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub enum TransportType {
4040

4141
pub struct ServerTester {
4242
url: String,
43-
transport_type: TransportType,
43+
pub transport_type: TransportType,
4444
http_config: Option<StreamableHttpTransportConfig>,
4545
json_rpc_client: Option<Client>,
4646
#[allow(dead_code)]
@@ -53,7 +53,7 @@ pub struct ServerTester {
5353
server_info: Option<InitializeResult>,
5454
tools: Option<Vec<ToolInfo>>,
5555
// Store the initialized pmcp client for reuse across tests
56-
pmcp_client: Option<pmcp::Client<StreamableHttpTransport>>,
56+
pub pmcp_client: Option<pmcp::Client<StreamableHttpTransport>>,
5757
stdio_client: Option<pmcp::Client<StdioTransport>>,
5858
}
5959

@@ -288,13 +288,33 @@ impl ServerTester {
288288
}
289289

290290
pub async fn run_tools_discovery(&mut self, test_all: bool) -> Result<TestReport> {
291+
self.run_tools_discovery_with_verbose(test_all, false).await
292+
}
293+
294+
pub async fn run_tools_discovery_with_verbose(
295+
&mut self,
296+
test_all: bool,
297+
verbose: bool,
298+
) -> Result<TestReport> {
291299
let mut report = TestReport::new();
292300
let start = Instant::now();
293301

294302
// Initialize
295303
let init_result = self.test_initialize().await;
296304
report.add_test(init_result.clone());
297305

306+
if verbose && init_result.status == TestStatus::Passed {
307+
println!(" ✓ Server initialized successfully");
308+
if let Some(ref server) = self.server_info {
309+
println!(
310+
" Server: {} v{}",
311+
server.server_info.name, server.server_info.version
312+
);
313+
}
314+
} else if verbose && init_result.status != TestStatus::Passed {
315+
println!(" ✗ Initialization failed: {:?}", init_result.error);
316+
}
317+
298318
if init_result.status != TestStatus::Passed {
299319
return Ok(report);
300320
}
@@ -303,6 +323,32 @@ impl ServerTester {
303323
let tools_result = self.test_tools_list().await;
304324
report.add_test(tools_result.clone());
305325

326+
if verbose {
327+
if tools_result.status == TestStatus::Passed {
328+
if let Some(ref tools) = self.tools {
329+
println!(" ✓ Found {} tools:", tools.len());
330+
for tool in tools {
331+
println!(
332+
" • {} - {}",
333+
tool.name,
334+
tool.description.as_deref().unwrap_or("No description")
335+
);
336+
}
337+
} else {
338+
println!(" ✓ No tools found");
339+
}
340+
} else {
341+
println!(" ✗ Failed to list tools: {:?}", tools_result.error);
342+
if verbose {
343+
// Print the actual error details
344+
println!(
345+
" Error details: {}",
346+
tools_result.error.as_deref().unwrap_or("Unknown error")
347+
);
348+
}
349+
}
350+
}
351+
306352
if tools_result.status == TestStatus::Passed && test_all {
307353
let tools_to_test: Vec<(String, Value)> = self
308354
.tools
@@ -319,7 +365,14 @@ impl ServerTester {
319365
.unwrap_or_default();
320366

321367
for (tool_name, test_args) in tools_to_test {
322-
report.add_test(self.test_tool(&tool_name, test_args).await?);
368+
let test_result = self.test_tool(&tool_name, test_args.clone()).await?;
369+
if verbose {
370+
println!(" Testing tool '{}': {:?}", tool_name, test_result.status);
371+
if test_result.status != TestStatus::Passed {
372+
println!(" Error: {:?}", test_result.error);
373+
}
374+
}
375+
report.add_test(test_result);
323376
}
324377
}
325378

examples/wasm-mcp-server/README.md

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ cargo build --target wasm32-unknown-unknown --release
4545
cd deployments/cloudflare
4646
make deploy
4747

48-
# Live at: https://mcp-sdk-worker.guy-ernest.workers.dev
48+
# Your deployment will be available at: https://<your-worker-name>.workers.dev
4949
```
5050

5151
### Deploy to Fermyon Spin
@@ -54,7 +54,7 @@ make deploy
5454
cd deployments/fermyon-spin
5555
make deploy
5656

57-
# Live at: https://mcp-fermyon-spin-3juc7zc4.fermyon.app/
57+
# Your deployment will be available at: https://<your-app-name>.fermyon.app/
5858
```
5959

6060
## 🏗️ Architecture
@@ -116,7 +116,58 @@ Reports the runtime environment (Cloudflare vs Fermyon)
116116

117117
## 🧪 Testing
118118

119-
### Test Any Deployment
119+
### Using MCP Tester with Scenario Files
120+
121+
The repository includes comprehensive test scenarios that can be run with the `mcp-tester` tool:
122+
123+
```bash
124+
# Test with simple calculator scenario
125+
mcp-tester scenario <deployment-url> test-scenarios/calculator-simple.json
126+
127+
# Test with comprehensive calculator tests (including error cases)
128+
mcp-tester scenario <deployment-url> test-scenarios/calculator-test.yaml
129+
130+
# Test with minimal tool listing
131+
mcp-tester scenario <deployment-url> test-scenarios/minimal-test.json
132+
```
133+
134+
#### Example: Testing Cloudflare Deployment
135+
```bash
136+
# From the rust-mcp-sdk root directory
137+
# Replace <your-worker-name> with your Cloudflare Worker subdomain
138+
./target/release/mcp-tester scenario \
139+
https://<your-worker-name>.workers.dev \
140+
examples/wasm-mcp-server/test-scenarios/calculator-test.yaml
141+
```
142+
143+
#### Example: Testing Fermyon Spin Deployment
144+
```bash
145+
# From the rust-mcp-sdk root directory
146+
# Replace <your-app-name> with your Fermyon app URL
147+
./target/release/mcp-tester scenario \
148+
https://<your-app-name>.fermyon.app/ \
149+
examples/wasm-mcp-server/test-scenarios/calculator-test.yaml
150+
```
151+
152+
### Available Test Scenarios
153+
154+
1. **`calculator-simple.json`** - Basic calculator operations
155+
- Tests addition, multiplication, division, and subtraction
156+
- Validates correct results for each operation
157+
158+
2. **`calculator-test.yaml`** - Comprehensive calculator test suite
159+
- Tests all arithmetic operations with various inputs
160+
- Tests negative numbers and decimals
161+
- Tests error handling (division by zero, invalid operations, missing parameters)
162+
- Tests large numbers and edge cases
163+
164+
3. **`minimal-test.json`** - Minimal connectivity test
165+
- Simply lists available tools
166+
- Quick smoke test for deployment health
167+
168+
### Manual Testing with curl
169+
170+
You can also test deployments manually:
120171

121172
```bash
122173
# Initialize connection
@@ -135,6 +186,17 @@ curl -X POST <deployment-url> \
135186
-d '{"jsonrpc":"2.0","id":"3","method":"tools/call","params":{"name":"calculator","arguments":{"operation":"add","a":5,"b":3}}}'
136187
```
137188

189+
### Building the MCP Tester
190+
191+
If you need to build the mcp-tester tool:
192+
193+
```bash
194+
# From the rust-mcp-sdk root directory
195+
cargo build --release --package mcp-server-tester
196+
197+
# The binary will be at: ./target/release/mcp-tester
198+
```
199+
138200
## 📊 Deployment Comparison
139201

140202
| Platform | Build Target | Runtime | Global Edge | Cold Start | State Management |
@@ -222,6 +284,9 @@ MIT
222284

223285
---
224286

225-
**Current Production Deployments:**
226-
- 🌐 Cloudflare: https://mcp-sdk-worker.guy-ernest.workers.dev
227-
- 🔄 Fermyon: https://mcp-fermyon-spin-3juc7zc4.fermyon.app/
287+
**Example Deployments for Testing:**
288+
You can test the MCP protocol with these example deployments:
289+
- 🌐 Cloudflare Example: https://mcp-sdk-worker.guy-ernest.workers.dev
290+
- 🔄 Fermyon Example: https://mcp-fermyon-spin-3juc7zc4.fermyon.app/
291+
292+
Note: These are example deployments for testing. Deploy your own instances using the instructions above.

examples/wasm-mcp-server/deployments/cloudflare/README.md

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,51 @@ make deploy
2424
make test-prod
2525
```
2626

27+
## Testing with MCP Tester
28+
29+
### Automated Scenario Testing
30+
31+
The deployment can be tested using the mcp-tester tool with predefined scenarios:
32+
33+
```bash
34+
# From the rust-mcp-sdk root directory
35+
# Replace <your-worker-name> with your deployed Worker subdomain
36+
37+
# Test with comprehensive calculator scenario
38+
./target/release/mcp-tester scenario \
39+
https://<your-worker-name>.workers.dev \
40+
examples/wasm-mcp-server/test-scenarios/calculator-test.yaml
41+
42+
# Quick connectivity test
43+
./target/release/mcp-tester scenario \
44+
https://<your-worker-name>.workers.dev \
45+
examples/wasm-mcp-server/test-scenarios/minimal-test.json
46+
47+
# Basic calculator operations test
48+
./target/release/mcp-tester scenario \
49+
https://<your-worker-name>.workers.dev \
50+
examples/wasm-mcp-server/test-scenarios/calculator-simple.json
51+
```
52+
53+
### Expected Test Results
54+
55+
All scenarios should pass with output like:
56+
```
57+
╔════════════════════════════════════════════════════════════╗
58+
║ MCP SERVER TESTING TOOL v0.1.0 ║
59+
╚════════════════════════════════════════════════════════════╝
60+
61+
TEST RESULTS
62+
════════════════════════════════════════════════════════════
63+
✓ Test Addition - 10 + 5
64+
✓ Test Multiplication - 4 * 7
65+
✓ Test Division - 20 / 4
66+
✓ Test Division by Zero (error case)
67+
✓ Test Invalid Operation (error case)
68+
69+
SUMMARY: PASSED
70+
```
71+
2772
## Configuration
2873

2974
The `wrangler.toml` file specifies:
@@ -50,6 +95,11 @@ The `wrangler.toml` file specifies:
5095
- If runtime fails: Check the JavaScript wrapper initialization
5196
- For CORS issues: Headers are set in the Rust code
5297

53-
## Live Deployment
98+
## Deployment URL
99+
100+
After deploying, your MCP server will be available at:
101+
🌐 `https://<your-worker-name>.workers.dev`
54102

103+
### Example Deployment for Testing
104+
You can test the MCP protocol with this example deployment:
55105
🌐 https://mcp-sdk-worker.guy-ernest.workers.dev

0 commit comments

Comments
 (0)