From 2acfeb44531ffe5cff855debbbfd7e28b4972714 Mon Sep 17 00:00:00 2001 From: claude Date: Thu, 6 Aug 2026 01:13:58 +0400 Subject: [PATCH] config: the MCP block itself joins mcp.go (V-410) Second half of the move. MCPConfig, DefaultMCPHostInterval and the normalise arm now sit next to the server struct they govern; applyDefaults calls normaliseMCP instead of inlining it. Co-Authored-By: Claude Opus 5 --- internal/config/config.go | 49 +------------------------------------ internal/config/mcp.go | 51 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 48 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 03ce7bd..b766515 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -286,42 +286,6 @@ type Config struct { NetScan *NetScanConfig `json:"netscan,omitempty"` } -// MCPConfig — the MCP client block. Servers are dark until one has -// `"enabled": true`, and a discovered tool is only ever PROPOSED: Kami enables -// it on /tools, on the authed surface, exactly as he would a shell tool. The -// voice path can never grant a capability to itself. -type MCPConfig struct { - // Servers — the configured servers. Each needs exactly one of command - // (a subprocess on this box) or url (a streamable-HTTP endpoint). - Servers []MCPServerConfig `json:"servers,omitempty"` - - // Timeout — per-call budget for every server that does not set its own. - // 0 ⇒ mcp.DefaultTimeout (15s). A tool slower than this is not usable in a - // spoken turn. - Timeout Duration `json:"timeout,omitempty"` - - // AllowHosts / DenyHosts — the host lists for the shared webfetch door that - // url servers go through. Deny wins. Private addresses are refused - // unconditionally unless the individual server sets allow_private. - AllowHosts []string `json:"allow_hosts,omitempty"` - DenyHosts []string `json:"deny_hosts,omitempty"` - - // MaxBytes — cap on one JSON-RPC response. 0 ⇒ webfetch.DefaultMaxBytes. - MaxBytes int64 `json:"max_bytes,omitempty"` - - // HostInterval — minimum spacing between two requests to one MCP server. - // 0 ⇒ DefaultMCPHostInterval (50ms), NOT webfetch's own one-second default. - // That default was sized for a feed poll loop, and this path is in a spoken - // turn: one dial is three requests (initialize, initialized, tools/list), - // so a second of spacing is two seconds of pure sleeping per dial and up to - // another second before every tools/call leaves the box. - HostInterval Duration `json:"host_interval,omitempty"` -} - -// DefaultMCPHostInterval — see MCPConfig.HostInterval. Enough to stop a -// runaway loop hammering a server, small enough not to be heard. -const DefaultMCPHostInterval = 50 * time.Millisecond - // SmartHomeConfig — the Home Assistant block (Vikunja #256). Dark until // `"enabled": true`, and even then a discovered device is only ever PROPOSED // into the act allowlist: Kami enables it on /tools, behind step-up, exactly as @@ -1393,18 +1357,7 @@ func (c *Config) applyDefaults() { c.Feeds = nil } - // Same rule for MCP: a block with no server at all is the same as no block. - // A block whose servers are all disabled is NOT normalised away, because - // validate has to see their shape — a dark block with a typo in it should - // fail at startup, which is the whole reason it can be written before it is - // switched on. wireMCP builds nothing when nothing is enabled, so "off" - // still holds. - if c.MCP != nil && len(c.MCP.Servers) == 0 { - c.MCP = nil - } - if c.MCP != nil && c.MCP.HostInterval <= 0 { - c.MCP.HostInterval = Duration(DefaultMCPHostInterval) - } + c.normaliseMCP() // Same rule for the house: a block that is not enabled is the same as no // block at all, so "off" stays in one place. diff --git a/internal/config/mcp.go b/internal/config/mcp.go index f8da91c..3fd2e65 100644 --- a/internal/config/mcp.go +++ b/internal/config/mcp.go @@ -6,6 +6,57 @@ import ( "github.com/kami/maven/internal/mcp" ) +// MCPConfig — the MCP client block. Servers are dark until one has +// `"enabled": true`, and a discovered tool is only ever PROPOSED: Kami enables +// it on /tools, on the authed surface, exactly as he would a shell tool. The +// voice path can never grant a capability to itself. +type MCPConfig struct { + // Servers — the configured servers. Each needs exactly one of command + // (a subprocess on this box) or url (a streamable-HTTP endpoint). + Servers []MCPServerConfig `json:"servers,omitempty"` + + // Timeout — per-call budget for every server that does not set its own. + // 0 ⇒ mcp.DefaultTimeout (15s). A tool slower than this is not usable in a + // spoken turn. + Timeout Duration `json:"timeout,omitempty"` + + // AllowHosts / DenyHosts — the host lists for the shared webfetch door that + // url servers go through. Deny wins. Private addresses are refused + // unconditionally unless the individual server sets allow_private. + AllowHosts []string `json:"allow_hosts,omitempty"` + DenyHosts []string `json:"deny_hosts,omitempty"` + + // MaxBytes — cap on one JSON-RPC response. 0 ⇒ webfetch.DefaultMaxBytes. + MaxBytes int64 `json:"max_bytes,omitempty"` + + // HostInterval — minimum spacing between two requests to one MCP server. + // 0 ⇒ DefaultMCPHostInterval (50ms), NOT webfetch's own one-second default. + // That default was sized for a feed poll loop, and this path is in a spoken + // turn: one dial is three requests (initialize, initialized, tools/list), + // so a second of spacing is two seconds of pure sleeping per dial and up to + // another second before every tools/call leaves the box. + HostInterval Duration `json:"host_interval,omitempty"` +} + +// DefaultMCPHostInterval — see MCPConfig.HostInterval. Enough to stop a +// runaway loop hammering a server, small enough not to be heard. +const DefaultMCPHostInterval = 50 * time.Millisecond + +// normaliseMCP applies the block's defaults. A block with no server at all is +// the same as no block. A block whose servers are all disabled is NOT +// normalised away, because validate has to see their shape — a dark block with +// a typo in it should fail at startup, which is the whole reason it can be +// written before it is switched on. wireMCP builds nothing when nothing is +// enabled, so "off" still holds. +func (c *Config) normaliseMCP() { + if c.MCP != nil && len(c.MCP.Servers) == 0 { + c.MCP = nil + } + if c.MCP != nil && c.MCP.HostInterval <= 0 { + c.MCP.HostInterval = Duration(DefaultMCPHostInterval) + } +} + // MCPServerConfig — one MCP server. type MCPServerConfig struct { // Name — the local handle. It prefixes every tool this server contributes