Skip to content

Commit 5802a1f

Browse files
committed
Fix mcp.transport=embed, add /mcp add|del
1 parent 0f1cf93 commit 5802a1f

6 files changed

Lines changed: 169 additions & 7 deletions

File tree

src/repl/conf.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ func NewConfigOptions() *ConfigOptions {
150150
co.RegisterOption("mcp.config", StringOption, "Path to MCP configuration file", "")
151151
co.RegisterOption("mcp.args", StringOption, "Command-line arguments to pass to mai-wmcp", "")
152152
co.RegisterOption("mcp.daemon", BooleanOption, "Enable starting the mai-wmcp server", "true")
153-
co.RegisterOption("mcp.transport", StringOption, "MCP transport: http (default, spawns mai-wmcp) or embed (in-process via wmcplib)", "http")
153+
co.RegisterOption("mcp.transport", StringOption, "MCP transport: embed (default, in-process via wmcplib) or http (spawns mai-wmcp)", "embed")
154154
co.RegisterOption("mcp.yolo", BooleanOption, "Skip tool confirmation prompts when running in embed transport", "false")
155155
co.RegisterOption("mcp.debug", BooleanOption, "Enable debug output for MCP communication between agent, model, and servers", "false")
156156
co.RegisterOption("mcp.prompt", StringOption, "Custom text to be included in the instructions prompt for react loops", "")
@@ -624,6 +624,13 @@ func (r *REPL) handleSetCommand(args []string) (string, error) {
624624
}
625625
_ = r.configOptions.Set("mcp.reason", valLower)
626626
return "", nil
627+
case "mcp.transport":
628+
valLower := strings.ToLower(value)
629+
if valLower != "http" && valLower != "embed" {
630+
return fmt.Sprintf("Error: invalid value '%s' for mcp.transport. Must be one of: embed, http\r\n", value), nil
631+
}
632+
_ = r.configOptions.Set("mcp.transport", valLower)
633+
return "", nil
627634
}
628635

629636
// Set the option value with validation

src/repl/repl.go

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ func (r *REPL) handleMCPCommand(args []string) (string, error) {
237237
if len(args) < 2 {
238238
var output strings.Builder
239239
output.WriteString("MCP server management commands:\r\n")
240+
output.WriteString(" /mcp add <name> <command> [args...] - Add a new MCP server (enabled by default)\r\n")
241+
output.WriteString(" /mcp del <server> - Remove MCP server from configuration\r\n")
240242
output.WriteString(" /mcp start [server] - Start MCP server(s) (all enabled if no server specified)\r\n")
241243
output.WriteString(" /mcp stop [server] - Stop MCP server(s) (all if no server specified)\r\n")
242244
output.WriteString(" /mcp restart [server] - Restart MCP server(s)\r\n")
@@ -249,6 +251,10 @@ func (r *REPL) handleMCPCommand(args []string) (string, error) {
249251

250252
action := args[1]
251253
switch action {
254+
case "add":
255+
return r.handleMCPAdd(args[2:])
256+
case "del", "rm", "remove":
257+
return r.handleMCPDel(args[2:])
252258
case "start":
253259
return r.handleMCPStart(args[2:])
254260
case "stop":
@@ -268,6 +274,97 @@ func (r *REPL) handleMCPCommand(args []string) (string, error) {
268274
}
269275
}
270276

277+
// handleMCPAdd adds a new MCP server to the configuration.
278+
// Accepts either:
279+
// /mcp add <name> <http-or-sse-url>
280+
// /mcp add <name> <command> [args...]
281+
// A single remainder argument starting with http:// or https:// is stored as a
282+
// URL server (type=http, or type=sse if the URL path ends with /sse). Anything
283+
// else is treated as a shell one-liner and split into command + args.
284+
func (r *REPL) handleMCPAdd(args []string) (string, error) {
285+
if len(args) < 2 {
286+
return "Usage: /mcp add <name> <url-or-command> [args...]\r\n", nil
287+
}
288+
289+
name := args[0]
290+
if r.mcpConfig.Servers == nil {
291+
r.mcpConfig.Servers = make(map[string]MCPServer)
292+
}
293+
if _, exists := r.mcpConfig.Servers[name]; exists {
294+
return fmt.Sprintf("Server %s already exists. Use /mcp del %s first or /mcp edit to modify it.\r\n", name, name), nil
295+
}
296+
297+
server := MCPServer{Enabled: true}
298+
first := args[1]
299+
rest := args[2:]
300+
301+
if isMCPURL(first) && len(rest) == 0 {
302+
server.Type = "http"
303+
if strings.HasSuffix(strings.TrimRight(first, "/"), "/sse") {
304+
server.Type = "sse"
305+
}
306+
server.URL = first
307+
} else {
308+
parts := []string{first}
309+
if len(rest) == 0 {
310+
// Single remainder may itself be a shell-style one-liner.
311+
parts = strings.Fields(first)
312+
if len(parts) == 0 {
313+
return "Usage: /mcp add <name> <url-or-command> [args...]\r\n", nil
314+
}
315+
} else {
316+
parts = append(parts, rest...)
317+
}
318+
server.Type = "stdio"
319+
server.Command = parts[0]
320+
if len(parts) > 1 {
321+
server.Args = append(server.Args, parts[1:]...)
322+
}
323+
}
324+
325+
r.mcpConfig.Servers[name] = server
326+
327+
if err := r.saveMCPConfig(); err != nil {
328+
return fmt.Sprintf("Failed to save config: %v\r\n", err), nil
329+
}
330+
331+
if server.Type == "stdio" {
332+
return fmt.Sprintf("Added MCP server %s (stdio: %s)\r\n", name, formatCommandString(append([]string{server.Command}, server.Args...))), nil
333+
}
334+
return fmt.Sprintf("Added MCP server %s (%s: %s)\r\n", name, server.Type, server.URL), nil
335+
}
336+
337+
// isMCPURL reports whether s looks like an http(s) URL suitable for an MCP
338+
// HTTP/SSE transport.
339+
func isMCPURL(s string) bool {
340+
s = strings.ToLower(strings.TrimSpace(s))
341+
return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://")
342+
}
343+
344+
// handleMCPDel removes an MCP server from the configuration
345+
func (r *REPL) handleMCPDel(args []string) (string, error) {
346+
if len(args) == 0 {
347+
return "Usage: /mcp del <server>\r\n", nil
348+
}
349+
350+
var output strings.Builder
351+
for _, name := range args {
352+
if _, exists := r.mcpConfig.Servers[name]; !exists {
353+
fmt.Fprintf(&output, "Server %s not found\r\n", name)
354+
continue
355+
}
356+
_ = r.stopMCPServer(name)
357+
delete(r.mcpConfig.Servers, name)
358+
fmt.Fprintf(&output, "Removed %s\r\n", name)
359+
}
360+
361+
if err := r.saveMCPConfig(); err != nil {
362+
fmt.Fprintf(&output, "Failed to save config: %v\r\n", err)
363+
}
364+
365+
return output.String(), nil
366+
}
367+
271368
// handleMCPStart starts MCP servers
272369
func (r *REPL) handleMCPStart(servers []string) (string, error) {
273370
var output strings.Builder
@@ -2245,7 +2342,10 @@ func (r *REPL) handleCompactCommand() error {
22452342
return nil
22462343
}
22472344

2248-
// handleToolCommand executes the mai-tool command with the given arguments
2345+
// handleToolCommand executes the mai-tool command with the given arguments.
2346+
// When mcp.transport=embed, list/call/prompts subcommands are served from the
2347+
// in-process wmcplib service so they see the same set of MCP servers managed
2348+
// by /mcp instead of the external mai-wmcp HTTP daemon.
22492349
func (r *REPL) handleToolCommand(args []string) (string, error) {
22502350
if len(args) < 2 {
22512351
tools, err := GetAvailableToolsWithStatus(r.configOptions, r.agentConfig)
@@ -2254,8 +2354,48 @@ func (r *REPL) handleToolCommand(args []string) (string, error) {
22542354
}
22552355
return tools + "\n", nil
22562356
}
2357+
2358+
subArgs := args[1:]
2359+
if replEmbedActive(r) {
2360+
if _, err := embedGetService(r); err != nil {
2361+
return "", fmt.Errorf("embed transport: %v", err)
2362+
}
2363+
switch subArgs[0] {
2364+
case "list":
2365+
format := Markdown
2366+
if v := strings.TrimSpace(r.configOptions.Get("mcp.toolformat")); v != "" && v != "?" {
2367+
format = parseToolFormat(v)
2368+
}
2369+
out, err := embedListToolsFormatted(r, format)
2370+
if err != nil {
2371+
return "", err
2372+
}
2373+
if !strings.HasSuffix(out, "\n") {
2374+
out += "\n"
2375+
}
2376+
return out, nil
2377+
case "call":
2378+
if len(subArgs) < 2 {
2379+
return "Usage: /tool call <name> [key=value ...]\r\n", nil
2380+
}
2381+
out, err := embedCallToolStringArgs(r, subArgs[1], subArgs[2:], 60)
2382+
if err != nil {
2383+
return "", err
2384+
}
2385+
if !strings.HasSuffix(out, "\n") {
2386+
out += "\n"
2387+
}
2388+
return out, nil
2389+
case "prompts":
2390+
if len(subArgs) >= 2 && subArgs[1] == "list" {
2391+
return embedListPromptsSimple(r)
2392+
}
2393+
}
2394+
return fmt.Sprintf("Subcommand '%s' not supported under mcp.transport=embed. Use /set mcp.transport=http to fall back to mai-tool.\r\n", strings.Join(subArgs, " ")), nil
2395+
}
2396+
22572397
// Execute mai-tool directly with the provided arguments
2258-
cmd := exec.Command("mai-tool", args[1:]...)
2398+
cmd := exec.Command("mai-tool", subArgs...)
22592399
output, err := cmd.CombinedOutput()
22602400
if err != nil {
22612401
return "", fmt.Errorf("mai-tool execution failed: %v\n%s", err, string(output))

src/repl/repl_commands.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ func (r *REPL) initCommands() {
130130
// MCP command: manage MCP servers
131131
r.commands["/mcp"] = Command{
132132
Name: "/mcp",
133-
Description: "Manage MCP servers (start, stop, restart, enable, disable, edit, status)",
133+
Description: "Manage MCP servers (add, del, start, stop, restart, enable, disable, edit, status)",
134134
Handler: func(r *REPL, args []string) (string, error) {
135135
return r.handleMCPCommand(args)
136136
},

src/repl/repl_core.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ func (r *REPL) cleanup() {
409409
}
410410
if mode == "prompt" {
411411
if !AskYesNo("Save session?", 'y') {
412+
fmt.Fprintf(os.Stderr, "\r")
412413
return
413414
}
414415
}

src/repl/repl_types.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,10 @@ type MCPConfig struct {
7070

7171
// MCPServer represents a single MCP server configuration
7272
type MCPServer struct {
73-
Command string `json:"command"`
73+
Type string `json:"type,omitempty"`
74+
Command string `json:"command,omitempty"`
7475
Args []string `json:"args,omitempty"`
76+
URL string `json:"url,omitempty"`
7577
Env map[string]string `json:"env,omitempty"`
7678
Enabled bool `json:"enabled"`
7779
}

src/wmcp/lib/config.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,10 @@ type MAIConfig struct {
157157

158158
// MAIServer represents a server in the MAI config format
159159
type MAIServer struct {
160-
Command string `json:"command"`
160+
Type string `json:"type,omitempty"`
161+
Command string `json:"command,omitempty"`
161162
Args []string `json:"args,omitempty"`
163+
URL string `json:"url,omitempty"`
162164
Env map[string]string `json:"env,omitempty"`
163165
Enabled bool `json:"enabled"`
164166
Tools map[string]bool `json:"tools,omitempty"`
@@ -190,10 +192,20 @@ func LoadMAIConfig(configPath string) (*Config, error) {
190192
continue
191193
}
192194

195+
srvType := server.Type
196+
if srvType == "" {
197+
if server.URL != "" {
198+
srvType = "http"
199+
} else {
200+
srvType = "stdio"
201+
}
202+
}
203+
193204
config.MCPServers[name] = MCPServerConfig{
194-
Type: "stdio",
205+
Type: srvType,
195206
Command: server.Command,
196207
Args: server.Args,
208+
URL: server.URL,
197209
Env: server.Env,
198210
Tools: server.Tools,
199211
}

0 commit comments

Comments
 (0)