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 <noreply@anthropic.com>
This commit is contained in:
@@ -286,42 +286,6 @@ type Config struct {
|
|||||||
NetScan *NetScanConfig `json:"netscan,omitempty"`
|
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
|
// SmartHomeConfig — the Home Assistant block (Vikunja #256). Dark until
|
||||||
// `"enabled": true`, and even then a discovered device is only ever PROPOSED
|
// `"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
|
// 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
|
c.Feeds = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same rule for MCP: a block with no server at all is the same as no block.
|
c.normaliseMCP()
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same rule for the house: a block that is not enabled is the same as no
|
// 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.
|
// block at all, so "off" stays in one place.
|
||||||
|
|||||||
@@ -6,6 +6,57 @@ import (
|
|||||||
"github.com/kami/maven/internal/mcp"
|
"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.
|
// MCPServerConfig — one MCP server.
|
||||||
type MCPServerConfig struct {
|
type MCPServerConfig struct {
|
||||||
// Name — the local handle. It prefixes every tool this server contributes
|
// Name — the local handle. It prefixes every tool this server contributes
|
||||||
|
|||||||
Reference in New Issue
Block a user