mcp,tools,main: shut down MCP servers on agent switch
Client gains a Close() method that closes stdin (giving the subprocess a clean EOF) then kills and reaps the process. Executor.Close() calls Close() on all registered clients. agentEnv now carries the mcpExec so the /agent command can close the outgoing executor before building the new one. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
fd5de0dec3
commit
ea07f6d3a0
8
main.go
8
main.go
|
|
@ -204,6 +204,7 @@ type model struct {
|
|||
backendName string // e.g. "ollama", "openrouter", "openai"
|
||||
agentName string // active agent config name, e.g. "default"
|
||||
agentsDir string // path to ~/.config/ollie/agents/
|
||||
mcpExec *tools.Executor // current agent's MCP executor; closed on agent switch
|
||||
builtinExec *execpkg.Executor
|
||||
confirmPtr *agent.ConfirmFn // indirection so startAgent can set it per-run
|
||||
ctxOverhead int // fixed per-request char overhead (system prompt + tool schemas)
|
||||
|
|
@ -243,6 +244,7 @@ type timeoutMsg struct{}
|
|||
|
||||
// agentEnv holds the runtime state derived from an agent config file.
|
||||
type agentEnv struct {
|
||||
mcpExec *tools.Executor // kept so it can be closed on agent switch
|
||||
tools []backend.Tool
|
||||
exec agent.ToolExecutor
|
||||
confirm *agent.ConfirmFn // pointer filled in by startAgent per-run
|
||||
|
|
@ -339,6 +341,7 @@ func buildAgentEnv(cfg *config.Config, builtinExec *execpkg.Executor) agentEnv {
|
|||
}
|
||||
|
||||
return agentEnv{
|
||||
mcpExec: mcpExec,
|
||||
tools: allTools,
|
||||
exec: execFn,
|
||||
confirm: confirmPtr,
|
||||
|
|
@ -445,6 +448,7 @@ func main() {
|
|||
backendName: backendName,
|
||||
agentName: agentName,
|
||||
agentsDir: agentsDir,
|
||||
mcpExec: env.mcpExec,
|
||||
builtinExec: builtinExec,
|
||||
confirmPtr: env.confirm,
|
||||
ctxOverhead: env.ctxOverhead,
|
||||
|
|
@ -856,7 +860,11 @@ func (m *model) handleCommand(input string) bool {
|
|||
m.display = append(m.display, fmt.Sprintf("Error: agent %q: %v", name, err))
|
||||
return true
|
||||
}
|
||||
if m.mcpExec != nil {
|
||||
m.mcpExec.Close()
|
||||
}
|
||||
env := buildAgentEnv(cfg, m.builtinExec)
|
||||
m.mcpExec = env.mcpExec
|
||||
m.hooks = env.hooks
|
||||
m.loopcfg.SystemPrompt = env.systemPrompt
|
||||
m.loopcfg.Tools = env.tools
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
|
@ -32,9 +33,11 @@ func (e *RPCError) Error() string {
|
|||
// of a subprocess).
|
||||
type Client struct {
|
||||
mu sync.Mutex
|
||||
wc io.WriteCloser
|
||||
w io.Writer
|
||||
sc *bufio.Scanner
|
||||
nextID int
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
// NewClient creates a Client from a reader and writer.
|
||||
|
|
@ -42,6 +45,26 @@ func NewClient(r io.Reader, w io.Writer) *Client {
|
|||
return &Client{w: w, sc: bufio.NewScanner(r)}
|
||||
}
|
||||
|
||||
// newClientWithProcess creates a Client that owns the given subprocess.
|
||||
// Close() will shut it down.
|
||||
func newClientWithProcess(r io.Reader, wc io.WriteCloser, cmd *exec.Cmd) *Client {
|
||||
return &Client{wc: wc, w: wc, sc: bufio.NewScanner(r), cmd: cmd}
|
||||
}
|
||||
|
||||
// Close shuts down the underlying subprocess: closes its stdin so it receives
|
||||
// EOF, then kills the process if it is still running.
|
||||
func (c *Client) Close() error {
|
||||
var err error
|
||||
if c.wc != nil {
|
||||
err = c.wc.Close()
|
||||
}
|
||||
if c.cmd != nil && c.cmd.Process != nil {
|
||||
c.cmd.Process.Kill()
|
||||
c.cmd.Wait() // reap the process; ignore errors
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Call sends a JSON-RPC request and returns the result.
|
||||
func (c *Client) Call(method string, params interface{}) (json.RawMessage, error) {
|
||||
c.mu.Lock()
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ func (t *STDIOTransport) Connect() (*Client, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
client := NewClient(stdout, stdin)
|
||||
client := newClientWithProcess(stdout, stdin, cmd)
|
||||
|
||||
// MCP initialization handshake.
|
||||
if _, err := client.Call("initialize", map[string]interface{}{
|
||||
|
|
|
|||
|
|
@ -29,6 +29,13 @@ func (e *Executor) AddServer(name string, client *mcp.Client) {
|
|||
e.servers[name] = client
|
||||
}
|
||||
|
||||
// Close shuts down all connected MCP servers.
|
||||
func (e *Executor) Close() {
|
||||
for _, client := range e.servers {
|
||||
client.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// ListTools returns all tools advertised by all connected servers.
|
||||
func (e *Executor) ListTools() ([]ToolInfo, error) {
|
||||
var all []ToolInfo
|
||||
|
|
|
|||
Reference in New Issue