- 1
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] - 2
- 3
use std::collections::HashMap; - 4
use std::path::PathBuf; - 5
use std::sync::Arc; - 6
- 7
use serde_json::json; - 8
- 9
use vak_mcp::{McpManager, McpTool}; - 10
use vak_tools::{Tool, ToolContext}; - 11
- 12
fn server_script() -> PathBuf { - 13
let manifest = env!("CARGO_MANIFEST_DIR"); - 14
PathBuf::from(manifest) - 15
.parent() - 16
.unwrap() - 17
.parent() - 18
.unwrap() - 19
.join("scripts/fake_mcp_server.py") - 20
} - 21
- 22
fn manager() -> Arc<McpManager> { - 23
manager_with_env(Vec::new()) - 24
} - 25
- 26
fn manager_with_env(env: Vec<(String, String)>) -> Arc<McpManager> { - 27
let mut servers = HashMap::new(); - 28
servers.insert( - 29
"fake".to_string(), - 30
vak_mcp::ServerConfig { - 31
command: "python3".into(), - 32
args: vec![server_script().display().to_string()], - 33
env, - 34
network: false, - 35
}, - 36
); - 37
Arc::new(McpManager::new(servers, std::env::temp_dir())) - 38
} - 39
- 40
fn ctx() -> ToolContext { - 41
ToolContext::new(std::env::temp_dir()) - 42
} - 43
- 44
#[test] - 45
fn broker_schema_names_configured_servers_without_starting_them() { - 46
let manager = manager(); - 47
let tool = McpTool::new(manager.clone()); - 48
let schema = tool.schema(); - 49
assert_eq!(schema["properties"]["server"]["enum"], json!(["fake"])); - 50
assert!( - 51
manager.observations().is_empty(), - 52
"schema construction must preserve lazy MCP startup" - 53
); - 54
} - 55
- 56
#[tokio::test] - 57
async fn tool_scoped_allow_keeps_server_discoverable_without_exposing_other_tools() { - 58
let tool = McpTool::with_policy(manager(), Some(vec!["fake/echo".into()]), Vec::new()); - 59
assert_eq!( - 60
tool.schema()["properties"]["server"]["enum"], - 61
json!(["fake"]) - 62
); - 63
let out = tool - 64
.execute(&json!({"action": "list", "server": "fake"}), &ctx()) - 65
.await; - 66
assert!(!out.is_error, "{}", out.content); - 67
assert!(out.content.contains("echo —"), "{}", out.content); - 68
assert!(!out.content.contains("boom —"), "{}", out.content); - 69
let denied = tool - 70
.execute( - 71
&json!({"action": "call", "server": "fake", "tool": "boom"}), - 72
&ctx(), - 73
) - 74
.await; - 75
assert!(denied.is_error); - 76
assert!(denied.content.contains("denied by channel policy")); - 77
} - 78
- 79
#[tokio::test] - 80
async fn list_discovers_server_and_tools() { - 81
let tool = McpTool::new(manager()); - 82
let out = tool - 83
.execute(&json!({"action": "list", "server": "fake"}), &ctx()) - 84
.await; - 85
assert!(!out.is_error, "list failed: {}", out.content); - 86
assert!(out.content.contains("fake:"), "got: {}", out.content); - 87
assert!(out.content.contains("echo — Echo back")); - 88
} - 89
- 90
#[tokio::test] - 91
async fn call_invokes_tool_and_returns_text() { - 92
let tool = McpTool::new(manager()); - 93
let out = tool - 94
.execute( - 95
&json!({ - 96
"action": "call", - 97
"server": "fake", - 98
"tool": "echo", - 99
"arguments": {"text": "hello mcp"} - 100
}), - 101
&ctx(), - 102
) - 103
.await; - 104
assert!(!out.is_error, "call failed: {}", out.content); - 105
assert_eq!(out.content.trim(), "echo: hello mcp"); - 106
} - 107
- 108
#[tokio::test] - 109
async fn a_call_returns_the_whole_result() { - 110
let tool = McpTool::new(manager()); - 111
let out = tool - 112
.execute( - 113
&json!({ - 114
"action": "call", - 115
"server": "fake", - 116
"tool": "echo", - 117
"arguments": {"text": "x".repeat(50_000)} - 118
}), - 119
&ctx(), - 120
) - 121
.await; - 122
assert!(!out.is_error, "{}", out.content); - 123
assert!(out.content.contains(&"x".repeat(50_000))); - 124
} - 125
- 126
#[tokio::test] - 127
async fn server_side_tool_errors_become_error_values() { - 128
let tool = McpTool::new(manager()); - 129
let out = tool - 130
.execute( - 131
&json!({ - 132
"action": "call", - 133
"server": "fake", - 134
"tool": "boom" - 135
}), - 136
&ctx(), - 137
) - 138
.await; - 139
assert!(out.is_error); - 140
assert!(out.content.contains("boom failed on purpose")); - 141
} - 142
- 143
#[tokio::test] - 144
async fn configured_secrets_are_redacted_from_mcp_results_and_errors() { - 145
let secret = "tavily-test-secret-value"; - 146
let tool = McpTool::new(manager_with_env(vec![( - 147
"MCP_TEST_SECRET".into(), - 148
secret.into(), - 149
)])); - 150
- 151
let result = tool - 152
.execute( - 153
&json!({"action": "call", "server": "fake", "tool": "secret_result"}), - 154
&ctx(), - 155
) - 156
.await; - 157
assert!(!result.is_error); - 158
assert!(!result.content.contains(secret), "got: {}", result.content); - 159
assert!(result.content.contains("[REDACTED]")); - 160
- 161
let error = tool - 162
.execute( - 163
&json!({"action": "call", "server": "fake", "tool": "boom"}), - 164
&ctx(), - 165
) - 166
.await; - 167
assert!(error.is_error); - 168
assert!(!error.content.contains(secret), "got: {}", error.content); - 169
assert!(error.content.contains("[REDACTED]")); - 170
} - 171
- 172
#[tokio::test] - 173
async fn unknown_tool_is_rejected_from_discovered_catalog() { - 174
let tool = McpTool::new(manager()); - 175
let out = tool - 176
.execute( - 177
&json!({ - 178
"action": "call", - 179
"server": "fake", - 180
"tool": "search" - 181
}), - 182
&ctx(), - 183
) - 184
.await; - 185
assert!(out.is_error); - 186
assert!( - 187
out.content.contains("unknown tool 'search'"), - 188
"got: {}", - 189
out.content - 190
); - 191
assert!(out.content.contains("echo, boom"), "got: {}", out.content); - 192
} - 193
- 194
#[tokio::test] - 195
async fn unknown_server_is_an_error_value() { - 196
let tool = McpTool::new(manager()); - 197
let out = tool - 198
.execute( - 199
&json!({"action": "call", "server": "nope", "tool": "x"}), - 200
&ctx(), - 201
) - 202
.await; - 203
assert!(out.is_error); - 204
assert!(out.content.contains("cannot connect") || out.content.contains("unknown mcp server")); - 205
} - 206
- 207
#[tokio::test] - 208
async fn missing_server_is_an_error_value_before_spawn() { - 209
let tool = McpTool::new(manager()); - 210
let out = tool - 211
.execute( - 212
&json!({"action": "call", "tool": "echo", "arguments": {"text": "hello"}}), - 213
&ctx(), - 214
) - 215
.await; - 216
assert!(out.is_error); - 217
assert!(out.content.contains("missing required parameter: server")); - 218
} - 219
- 220
#[tokio::test] - 221
async fn malformed_arguments_remain_typed_error_values() { - 222
let tool = McpTool::new(manager()); - 223
let out = tool - 224
.execute( - 225
&json!({"action": "call", "server": "fake", "tool": "echo", "arguments": "not-an-object"}), - 226
&ctx(), - 227
) - 228
.await; - 229
assert!(out.is_error); - 230
assert!(out.content.contains("arguments") || out.content.contains("invalid")); - 231
} - 232
- 233
#[tokio::test] - 234
async fn missing_action_parameter_is_rejected() { - 235
let tool = McpTool::new(manager()); - 236
let out = tool.execute(&json!({}), &ctx()).await; - 237
assert!(out.is_error); - 238
assert!(out.content.contains("action")); - 239
} - 240
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.