Files
harness-sync/internal/adapter/common/mcp.go
T
lukaszraczylo 63bd976f4b fix(adapters): ground native schemas, merge-not-replace, add goose/cagent/zed
- common/mcp.go: MCPStyle enum + BuildMCPMapStyled for claude/crush/opencode/zed dialects
- common/jsonmerge.go: MergeJSONKeys (JSON-first, JSONC fallback) preserves user keys;
  fix: try plain json.Unmarshal before StripJSONComments to avoid mangling URLs with //
- common/yamlmerge.go: MergeYAMLKeys for YAML configs (goose)
- common/blocks.go: add ProvidersAsMap for opencode map-shaped provider key
- claudecode: merge settings.json (preserves hooks/permissions/env); MCPClaudeStyle (no type field)
- crush: fix mcp key (was mcpServers); MCPCrushStyle entries include type field; merge crush.json; fix import key mcp
- kilo: drop providers key (kilo has none); MCPOpencodeStyle (local/remote); merge kilo.json
- opencode: fix provider (singular map) + model (not default_model); MCPOpencodeStyle; merge jsonc; fix import key mcp
- goose: new adapter — YAML merge, extensions map, splitProviderModel, import reconstructs MCP from extensions
- cagent: new adapter — renders starter default.yaml, import returns empty
- zed: new adapter — merge settings.json, context_servers (MCPZedStyle) + agent.default_model
- docs: append Grounded harness schemas section to design spec
2026-05-23 03:23:02 +01:00

103 lines
2.1 KiB
Go

package common
import "github.com/lukaszraczylo/harness-sync/internal/canonical"
// MCPStyle selects the dialect for MCP server entries.
type MCPStyle int
const (
// MCPClaudeStyle: claude-code shape — {command, args, env, url, transport}.
MCPClaudeStyle MCPStyle = iota
// MCPCrushStyle: crush shape — adds type: stdio|http|sse.
MCPCrushStyle
// MCPOpencodeStyle: kilo + opencode shape — type: local|remote, command as []string.
MCPOpencodeStyle
// MCPZedStyle: zed context_servers — {enabled, source, command, args} or {enabled, url}.
MCPZedStyle
)
// BuildMCPMapStyled returns the MCP map for the given harness dialect.
func BuildMCPMapStyled(reg *canonical.MCPRegistry, style MCPStyle) map[string]any {
out := map[string]any{}
if reg == nil {
return out
}
for _, s := range reg.Servers {
out[s.Name] = mcpEntry(s, style)
}
return out
}
func mcpEntry(s canonical.MCPServer, style MCPStyle) map[string]any {
e := map[string]any{}
switch style {
case MCPClaudeStyle:
if s.Command != "" {
e["command"] = s.Command
}
if len(s.Args) > 0 {
e["args"] = s.Args
}
if s.URL != "" {
e["url"] = s.URL
}
if s.Transport != "" {
e["transport"] = s.Transport
}
if len(s.Env) > 0 {
e["env"] = s.Env
}
case MCPCrushStyle:
if s.URL != "" {
t := s.Transport
if t == "" {
t = "http"
}
e["type"] = t
e["url"] = s.URL
} else {
e["type"] = "stdio"
if s.Command != "" {
e["command"] = s.Command
}
if len(s.Args) > 0 {
e["args"] = s.Args
}
if len(s.Env) > 0 {
e["env"] = s.Env
}
}
case MCPOpencodeStyle:
if s.URL != "" {
e["type"] = "remote"
e["url"] = s.URL
} else {
e["type"] = "local"
cmd := []string{}
if s.Command != "" {
cmd = append(cmd, s.Command)
}
cmd = append(cmd, s.Args...)
e["command"] = cmd
if len(s.Env) > 0 {
e["environment"] = s.Env
}
}
e["enabled"] = true
case MCPZedStyle:
e["enabled"] = true
if s.URL != "" {
e["url"] = s.URL
} else {
e["source"] = "custom"
if s.Command != "" {
e["command"] = s.Command
}
if len(s.Args) > 0 {
e["args"] = s.Args
}
}
}
return e
}