11use anyhow:: Result ;
22use serde:: { Deserialize , Serialize } ;
33use serde_json:: { json, Value } ;
4+ use std:: collections:: HashSet ;
45use std:: io:: { BufRead , Write } ;
56use 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`).
424426pub 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