Skip to content

Commit 791dab8

Browse files
committed
Add /mcp update command to reload MCP servers without restart
- MCP Client: add Reconnect() method that closes old connections, rebuilds servers from new config, re-initializes, and re-embeds tools - Agent: add ReloadMCP() to expose reconnect to the handler layer - Telegram: add /mcp command with "update"/"reload" subcommands that re-reads config/mcp.json and reconnects all MCP servers https://claude.ai/code/session_01VtMjyBkcnrX8AZDuPj67XT
1 parent 6528549 commit 791dab8

3 files changed

Lines changed: 93 additions & 0 deletions

File tree

internal/agent/agent.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"time"
1010
"unicode/utf8"
1111

12+
"telegram-agent/internal/config"
1213
"telegram-agent/internal/llm"
1314
"telegram-agent/internal/mcp"
1415
"telegram-agent/internal/store"
@@ -231,6 +232,15 @@ func (a *Agent) EmbedText(ctx context.Context, text string) ([]float32, error) {
231232
return a.mcp.EmbedText(ctx, text)
232233
}
233234

235+
// ReloadMCP re-reads the MCP config file, reconnects all servers, and re-discovers tools.
236+
// Returns the number of tools available after reload.
237+
func (a *Agent) ReloadMCP(ctx context.Context, configs map[string]config.MCPServerConfig) (int, error) {
238+
if a.mcp == nil {
239+
return 0, fmt.Errorf("MCP client not initialized")
240+
}
241+
return a.mcp.Reconnect(ctx, configs)
242+
}
243+
234244
func (a *Agent) ListTools() []ToolInfo {
235245
var result []ToolInfo
236246
if a.mcp != nil {

internal/mcp/client.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,57 @@ func (c *Client) Close() {
228228
}
229229
}
230230

231+
// Reconnect closes existing connections, replaces the server list with new configs,
232+
// re-initializes all servers, and re-embeds tools if embeddings were enabled.
233+
// Returns the number of tools discovered after reconnect.
234+
func (c *Client) Reconnect(ctx context.Context, configs map[string]config.MCPServerConfig) (int, error) {
235+
// Close old connections.
236+
c.Close()
237+
238+
// Reset state.
239+
c.servers = make(map[string]*server)
240+
c.tools = nil
241+
c.toolServers = make(map[string]string)
242+
c.embeddingsReady = false
243+
244+
// Build new servers (same logic as NewClient).
245+
for name, cfg := range configs {
246+
if err := validateServerURL(cfg.URL); err != nil {
247+
c.logger.Warn("mcp server URL rejected", "server", name, "url", cfg.URL, "err", err)
248+
continue
249+
}
250+
srv := &server{
251+
name: name,
252+
url: cfg.URL,
253+
headers: cfg.Headers,
254+
http: &http.Client{Timeout: 30 * time.Second},
255+
}
256+
if len(cfg.AllowTools) > 0 {
257+
srv.allowTools = make(map[string]bool, len(cfg.AllowTools))
258+
for _, t := range cfg.AllowTools {
259+
srv.allowTools[t] = true
260+
}
261+
}
262+
if len(cfg.DenyTools) > 0 {
263+
srv.denyTools = make(map[string]bool, len(cfg.DenyTools))
264+
for _, t := range cfg.DenyTools {
265+
srv.denyTools[t] = true
266+
}
267+
}
268+
c.servers[name] = srv
269+
}
270+
271+
// Initialize and discover tools.
272+
c.Initialize(ctx)
273+
274+
// Re-embed tools if embeddings were configured.
275+
if c.embeddingCfg.APIKey != "" || c.embeddingCfg.BaseURL != "" {
276+
c.EmbedTools(ctx)
277+
}
278+
279+
return len(c.tools), nil
280+
}
281+
231282
// CallTool executes a tool on the appropriate MCP server.
232283
// On network failure it attempts one reconnect before giving up.
233284
func (c *Client) CallTool(ctx context.Context, name string, argsJSON string) (string, error) {

internal/telegram/handler.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,7 @@ func (h *Handler) handleCommand(msg *tgbotapi.Message) {
633633
"/model <name> — switch model\n"+
634634
"/model reset — back to auto\\-routing\n"+
635635
"/tools — list MCP tools\n"+
636+
"/mcp update — reload MCP servers\n"+
636637
"/help — this help\n\n"+
637638
"*Model:* `%s`\n"+
638639
"Responses longer than 4096 chars are sent as a `.md` file\\.",
@@ -690,6 +691,8 @@ func (h *Handler) handleCommand(msg *tgbotapi.Message) {
690691
h.bot.Send(msg) //nolint:errcheck
691692
case "tools":
692693
h.handleToolsCommand(chatID)
694+
case "mcp":
695+
h.handleMCPCommand(chatID, msg.CommandArguments())
693696
case "stats":
694697
h.handleStatsCommand(chatID)
695698
default:
@@ -991,13 +994,42 @@ func registerCommands(bot *tgbotapi.BotAPI) error {
991994
{Command: "model", Description: "Show / switch model"},
992995
{Command: "routing", Description: "Configure routing (inline UI)"},
993996
{Command: "tools", Description: "List connected MCP tools"},
997+
{Command: "mcp", Description: "MCP management (update/reload)"},
994998
{Command: "stats", Description: "Show history size, model, last compact"},
995999
{Command: "help", Description: "Help"},
9961000
}
9971001
_, err := bot.Request(tgbotapi.NewSetMyCommands(commands...))
9981002
return err
9991003
}
10001004

1005+
func (h *Handler) handleMCPCommand(chatID int64, args string) {
1006+
switch strings.TrimSpace(args) {
1007+
case "update", "reload":
1008+
h.sendPlain(chatID, "Reloading MCP servers...")
1009+
configs, err := config.LoadMCPServers("config/mcp.json")
1010+
if err != nil {
1011+
h.sendPlain(chatID, "Error loading mcp.json: "+err.Error())
1012+
return
1013+
}
1014+
if len(configs) == 0 {
1015+
h.sendPlain(chatID, "No MCP servers configured in mcp.json.")
1016+
return
1017+
}
1018+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
1019+
defer cancel()
1020+
toolCount, err := h.agent.ReloadMCP(ctx, configs)
1021+
if err != nil {
1022+
h.sendPlain(chatID, "Error: "+err.Error())
1023+
return
1024+
}
1025+
h.sendPlain(chatID, fmt.Sprintf("MCP reloaded: %d servers, %d tools.", len(configs), toolCount))
1026+
default:
1027+
h.send(chatID,
1028+
"*MCP commands:*\n\n"+
1029+
"/mcp update — reload mcp\\.json and reconnect all servers")
1030+
}
1031+
}
1032+
10011033
func (h *Handler) handleToolsCommand(chatID int64) {
10021034
tools := h.agent.ListTools()
10031035
if len(tools) == 0 {

0 commit comments

Comments
 (0)