@@ -58,9 +58,11 @@ func embedGetService(r *REPL) (*wmcplib.MCPService, error) {
5858
5959 yolo := false
6060 debug := false
61+ proxy := false
6162 if r != nil {
6263 yolo = r .configOptions .GetBool ("mcp.yolo" )
6364 debug = r .configOptions .GetBool ("mcp.debug" )
65+ proxy = r .configOptions .GetBool ("mcp.proxytools" )
6466 }
6567
6668 service := wmcplib .NewMCPService (wmcplib.Options {
@@ -71,6 +73,7 @@ func embedGetService(r *REPL) (*wmcplib.MCPService, error) {
7173 NonInteractive : cfg .MaiOptions .NonInteractive ,
7274 SessionMode : cfg .MaiOptions .SessionMode ,
7375 DebugMode : debug || cfg .MaiOptions .DebugMode ,
76+ ProxyToolsMode : proxy || cfg .MaiOptions .ProxyToolsMode ,
7477 Prompter : newReplPrompter (r ),
7578 })
7679
@@ -147,6 +150,10 @@ func embedListToolsFormatted(r *REPL, f Format) (string, error) {
147150 svc .Mutex .RLock ()
148151 defer svc .Mutex .RUnlock ()
149152
153+ if svc .ProxyToolsMode {
154+ return embedFormatProxyTools (f ), nil
155+ }
156+
150157 switch f {
151158 case JSON :
152159 res := make (map [string ][]wmcplib.Tool )
@@ -184,6 +191,96 @@ func embedListToolsFormatted(r *REPL, f Format) (string, error) {
184191 return embedFormatMarkdown (svc ), nil
185192}
186193
194+ // embedFormatProxyTools renders the two virtual proxy tools in the format
195+ // requested by the REPL. Used when the embedded service has ProxyToolsMode
196+ // enabled — the real underlying tools must not appear in the LLM-facing
197+ // catalog.
198+ func embedFormatProxyTools (f Format ) string {
199+ tools := wmcplib .ProxyTools ()
200+ switch f {
201+ case JSON :
202+ b , _ := json .Marshal (map [string ][]wmcplib.Tool {"proxy" : tools })
203+ return string (b )
204+ case XML :
205+ var out strings.Builder
206+ out .WriteString ("<tools>\n " )
207+ for _ , tool := range tools {
208+ out .WriteString (fmt .Sprintf (" <tool server=%q name=%q>\n " , "proxy" , tool .Name ))
209+ out .WriteString (fmt .Sprintf (" <description>%s</description>\n " , tool .Description ))
210+ for _ , p := range tool .Parameters {
211+ required := ""
212+ if p .Required {
213+ required = " required=\" true\" "
214+ }
215+ out .WriteString (fmt .Sprintf (" <param name=%q type=%q%s>%s</param>\n " ,
216+ p .Name , p .Type , required , p .Description ))
217+ }
218+ out .WriteString (" </tool>\n " )
219+ }
220+ out .WriteString ("</tools>\n " )
221+ return out .String ()
222+ case Simple :
223+ var out strings.Builder
224+ notFirst := false
225+ for _ , tool := range tools {
226+ if notFirst {
227+ out .WriteString ("--\n " )
228+ }
229+ notFirst = true
230+ out .WriteString (fmt .Sprintf ("TOOLNAME: %s\n " , tool .Name ))
231+ out .WriteString (fmt .Sprintf ("DESCRIPTION: %s\n " , tool .Description ))
232+ if len (tool .Parameters ) > 0 {
233+ var examples []string
234+ for _ , p := range tool .Parameters {
235+ examples = append (examples , fmt .Sprintf ("%s=<value>" , p .Name ))
236+ }
237+ out .WriteString (fmt .Sprintf ("USAGE: proxy %s %s\n " , tool .Name , strings .Join (examples , " " )))
238+ }
239+ }
240+ return out .String ()
241+ case Quiet :
242+ var out strings.Builder
243+ for _ , tool := range tools {
244+ out .WriteString (fmt .Sprintf ("- ToolName: %s\n " , tool .Name ))
245+ if tool .Description != "" {
246+ out .WriteString (fmt .Sprintf (" Description: %s\n " , tool .Description ))
247+ }
248+ if len (tool .Parameters ) > 0 {
249+ out .WriteString (" Parameters:\n " )
250+ for _ , p := range tool .Parameters {
251+ req := ""
252+ if p .Required {
253+ req = " [required]"
254+ }
255+ out .WriteString (fmt .Sprintf (" - %s=<%s> : %s%s\n " , p .Name , p .Type , p .Description , req ))
256+ }
257+ }
258+ }
259+ return strings .TrimRight (out .String (), "\n " )
260+ }
261+
262+ var out strings.Builder
263+ out .WriteString ("# Tools Catalog\n \n " )
264+ out .WriteString ("## Proxy Tools Mode\n \n " )
265+ out .WriteString ("Only two virtual tools are exposed; they gate access to the real underlying tools.\n \n " )
266+ for _ , tool := range tools {
267+ out .WriteString (fmt .Sprintf ("### %s\n " , tool .Name ))
268+ out .WriteString (fmt .Sprintf ("**Description:** %s\n \n " , tool .Description ))
269+ if len (tool .Parameters ) > 0 {
270+ out .WriteString ("**Parameters:**\n " )
271+ for _ , p := range tool .Parameters {
272+ req := ""
273+ if p .Required {
274+ req = " (required)"
275+ }
276+ out .WriteString (fmt .Sprintf ("- %s (%s)%s: %s\n " , p .Name , p .Type , req , p .Description ))
277+ }
278+ out .WriteString ("\n " )
279+ }
280+ }
281+ return out .String ()
282+ }
283+
187284func embedFormatQuiet (svc * wmcplib.MCPService ) string {
188285 categoryOrder := []string {"File" , "Analysis" , "Inspection" , "Metadata" , "Editing" }
189286 toolsByCategory := make (map [string ][]wmcplib.QuietToolEntry )
@@ -353,13 +450,32 @@ func embedCallTool(r *REPL, toolName string, args map[string]interface{}, timeou
353450 return "" , err
354451 }
355452
453+ _ = timeoutSeconds // the library imposes its own 30s stdio timeout for now
454+
455+ // In proxy mode the agent may only call the two virtual tools; route them
456+ // through ProcessMCPRequest which knows how to handle them.
457+ if svc .ProxyToolsMode {
458+ req := wmcplib.JSONRPCRequest {
459+ JSONRPC : "2.0" ,
460+ Method : "tools/call" ,
461+ Params : wmcplib.CallToolParams {Name : toolName , Arguments : args },
462+ ID : time .Now ().UnixNano (),
463+ }
464+ resp , _ := svc .ProcessMCPRequest (req )
465+ if resp == nil {
466+ return "" , nil
467+ }
468+ if resp .Error != nil {
469+ return "" , fmt .Errorf ("%v" , resp .Error )
470+ }
471+ return renderCallToolResult (resp .Result )
472+ }
473+
356474 server , resolvedName , err := svc .ResolveTool (toolName )
357475 if err != nil {
358476 return "" , err
359477 }
360478
361- _ = timeoutSeconds // the library imposes its own 30s stdio timeout for now
362-
363479 req := wmcplib.JSONRPCRequest {
364480 JSONRPC : "2.0" ,
365481 Method : "tools/call" ,
@@ -375,7 +491,14 @@ func embedCallTool(r *REPL, toolName string, args map[string]interface{}, timeou
375491 return "" , fmt .Errorf ("%v" , resp .Error )
376492 }
377493
378- resultBytes , _ := json .Marshal (resp .Result )
494+ return renderCallToolResult (resp .Result )
495+ }
496+
497+ // renderCallToolResult converts an MCP tools/call Result payload into the
498+ // plain-text representation the REPL expects (concatenated content text, or
499+ // raw JSON when the payload doesn't fit the CallToolResult shape).
500+ func renderCallToolResult (result interface {}) (string , error ) {
501+ resultBytes , _ := json .Marshal (result )
379502 var toolResult wmcplib.CallToolResult
380503 if err := json .Unmarshal (resultBytes , & toolResult ); err != nil {
381504 return string (resultBytes ), nil
0 commit comments