Skip to content

Commit 4ffaa18

Browse files
authored
mcp: expose discovered servers only via execute_tools (#562)
1 parent 5a2efa0 commit 4ffaa18

File tree

1 file changed

+23
-14
lines changed

1 file changed

+23
-14
lines changed

crates/pcb-mcp/src/lib.rs

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use anyhow::Result;
22
use serde::{Deserialize, Serialize};
33
use serde_json::{json, Value};
4+
use std::collections::HashSet;
45
use std::io::{BufRead, Write};
56
use std::sync::{Arc, Mutex};
67

@@ -419,13 +420,21 @@ pub fn eval_js(
419420
/// Run an MCP server that aggregates built-in tools with discovered external MCP servers
420421
///
421422
/// External servers are discovered by scanning PATH for `pcb-*` binaries and
422-
/// attempting to spawn them with an `mcp` subcommand. Tools from external servers
423-
/// are namespaced as `servername_toolname`.
423+
/// attempting to spawn them with an `mcp` subcommand. External tools are exposed
424+
/// only through `execute_tools` code mode; the direct MCP surface lists/calls only
425+
/// built-in tools (plus `execute_tools`).
424426
pub fn run_aggregated_server(
425427
builtin_tools: Vec<ToolInfo>,
426428
builtin_resources: Vec<ResourceInfo>,
427429
builtin_handler: ToolHandler,
428430
) -> Result<()> {
431+
let direct_tools = builtin_tools.clone();
432+
let direct_tool_names: HashSet<String> = direct_tools
433+
.iter()
434+
.map(|tool| tool.name.to_string())
435+
.collect();
436+
let direct_resources = builtin_resources.clone();
437+
429438
let aggregator = Arc::new(Mutex::new(McpAggregator::new(
430439
builtin_tools,
431440
builtin_resources,
@@ -471,9 +480,7 @@ pub fn run_aggregated_server(
471480
"ping" => json!({"jsonrpc": "2.0", "id": id, "result": {}}),
472481
"logging/setLevel" => json!({"jsonrpc": "2.0", "id": id, "result": {}}),
473482
"tools/list" => {
474-
let aggregator = aggregator.lock().expect("Lock poisoned");
475-
let mut tools = aggregator.all_tools();
476-
drop(aggregator);
483+
let mut tools = direct_tools.clone();
477484

478485
// Add meta-tools
479486
tools.extend(meta_tools.iter().cloned());
@@ -498,9 +505,7 @@ pub fn run_aggregated_server(
498505
json!({"jsonrpc": "2.0", "id": id, "result": {"tools": tool_list}})
499506
}
500507
"resources/list" => {
501-
let aggregator = aggregator.lock().expect("Lock poisoned");
502-
let resources = aggregator.all_resources();
503-
let resource_list: Vec<_> = resources
508+
let resource_list: Vec<_> = direct_resources
504509
.iter()
505510
.map(|r| {
506511
json!({
@@ -550,12 +555,16 @@ pub fn run_aggregated_server(
550555
}
551556
}
552557
Some(name) => {
553-
let ctx = McpContext::new(progress_token);
554-
let mut aggregator = aggregator.lock().expect("Lock poisoned");
555-
match aggregator.handle_tool_call(name, args, &ctx) {
556-
Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
557-
Err(e) => {
558-
json!({"jsonrpc": "2.0", "id": id, "error": {"code": -32000, "message": e.to_string()}})
558+
if !direct_tool_names.contains(name) {
559+
json!({"jsonrpc": "2.0", "id": id, "error": {"code": -32601, "message": format!("Unknown tool: {}", name)}})
560+
} else {
561+
let ctx = McpContext::new(progress_token);
562+
let mut aggregator = aggregator.lock().expect("Lock poisoned");
563+
match aggregator.handle_tool_call(name, args, &ctx) {
564+
Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
565+
Err(e) => {
566+
json!({"jsonrpc": "2.0", "id": id, "error": {"code": -32000, "message": e.to_string()}})
567+
}
559568
}
560569
}
561570
}

0 commit comments

Comments
 (0)