返回 DeepSeek-Reasonix
live.go
根目录 / internal / capdiag / live.go
1 package capdiag
2
3 import (
4 "context"
5 "fmt"
6 "os/exec"
7 "strings"
8 "time"
9
10 "reasonix/internal/boot"
11 "reasonix/internal/config"
12 "reasonix/internal/plugin"
13 )
14
15 func lookPath(cmd string) (string, error) {
16 return exec.LookPath(cmd)
17 }
18
19 // probeLiveMCP starts automatic-intent servers in an isolated Host, records
20 // connection results, and always closes the Host (including stdio children).
21 // Persistence (startup stats / schema cache) is disabled so --live stays
22 // free of cache/state side effects under Reasonix home.
23 func probeLiveMCP(rep *MCPReport, cfg *config.Config, root, home, reasonixHome string, timeout time.Duration) []Issue {
24 var issues []Issue
25 if cfg == nil {
26 return issues
27 }
28 if timeout <= 0 {
29 timeout = DefaultLiveTimeout
30 }
31 if timeout < MinLiveTimeout {
32 timeout = MinLiveTimeout
33 }
34 if timeout > MaxLiveTimeout {
35 timeout = MaxLiveTimeout
36 }
37
38 // Only probe automatic start intent servers.
39 var auto []config.PluginEntry
40 for _, p := range cfg.Plugins {
41 if p.ShouldAutoStart() {
42 auto = append(auto, p)
43 } else {
44 for i := range rep.Servers {
45 if rep.Servers[i].Name == p.Name {
46 rep.Servers[i].RuntimeStatus = "skipped"
47 }
48 }
49 }
50 }
51 if len(auto) == 0 {
52 return issues
53 }
54
55 specs := boot.PluginSpecsForRoot(auto, root)
56 ctx, cancel := context.WithTimeout(context.Background(), timeout*time.Duration(len(specs))+timeout)
57 defer cancel()
58
59 host, _, _ := plugin.Start(ctx, specs, plugin.StartPolicy{
60 PerPluginTimeout: timeout,
61 Concurrency: 4,
62 AbortOnError: false,
63 SkipPersistence: true,
64 })
65 if host == nil {
66 return issues
67 }
68 defer host.Close()
69
70 byName := map[string]int{}
71 for i, s := range rep.Servers {
72 byName[s.Name] = i
73 }
74
75 connected := map[string]bool{}
76 for _, s := range host.Servers() {
77 rep.bindings = append(rep.bindings, s.ToolBindings...)
78 connected[s.Name] = true
79 tools := make([]MCPToolInfo, 0, len(s.ToolList))
80 for _, t := range s.ToolList {
81 tools = append(tools, MCPToolInfo{Name: t.Name, ReadOnlyHint: t.ReadOnlyHint, DestructiveHint: t.DestructiveHint})
82 }
83 if i, ok := byName[s.Name]; ok {
84 rep.Servers[i].RuntimeStatus = "probed"
85 rep.Servers[i].ToolCount = s.Tools
86 rep.Servers[i].Tools = tools
87 } else {
88 rep.Servers = append(rep.Servers, MCPServerInfo{
89 Name: s.Name, Transport: s.Transport, RuntimeStatus: "probed",
90 ToolCount: s.Tools, Tools: tools, StartIntent: "automatic",
91 })
92 }
93 if s.HasTools && s.Tools == 0 {
94 issues = append(issues, Issue{
95 Severity: "warning", Code: "mcp.no_tools", Subsystem: "mcp",
96 Name: s.Name, Message: "live probe: MCP server connected but exposes no tools",
97 Remediation: "Check server configuration and authentication",
98 SettingsTab: "mcp",
99 })
100 }
101 }
102 for _, f := range host.Failures() {
103 errText := sanitizeErrTextWithPaths(f.Error, root, home, reasonixHome)
104 if i, ok := byName[f.Name]; ok {
105 rep.Servers[i].RuntimeStatus = "failed"
106 rep.Servers[i].Error = errText
107 rep.Servers[i].StartupStage = f.Stage
108 rep.Servers[i].StartupElapsedMS = f.Elapsed.Milliseconds()
109 rep.Servers[i].Stderr = sanitizeErrTextWithPaths(f.Stderr, root, home, reasonixHome)
110 }
111 issues = append(issues, Issue{
112 Severity: "error", Code: "mcp.start_failed", Subsystem: "mcp",
113 Name: f.Name, Message: "live probe failed: " + errText,
114 Remediation: "Fix command/URL/auth; re-run with --live after changes",
115 SettingsTab: "mcp",
116 })
117 }
118 for _, p := range auto {
119 if connected[p.Name] {
120 continue
121 }
122 if i, ok := byName[p.Name]; ok {
123 if rep.Servers[i].RuntimeStatus == "failed" {
124 continue
125 }
126 if rep.Servers[i].RuntimeStatus == "" {
127 rep.Servers[i].RuntimeStatus = "failed"
128 rep.Servers[i].Error = "no connection result within timeout"
129 issues = append(issues, Issue{
130 Severity: "error", Code: "mcp.start_failed", Subsystem: "mcp",
131 Name: p.Name, Message: fmt.Sprintf("live probe: no result within %s", timeout),
132 Remediation: "Increase --timeout or fix a hanging MCP server",
133 SettingsTab: "mcp",
134 })
135 }
136 }
137 }
138 return issues
139 }
140
141 // HasErrorSeverity reports whether the report contains any error-level issue.
142 func HasErrorSeverity(r Report) bool {
143 for _, is := range r.Issues {
144 if is.Severity == "error" {
145 return true
146 }
147 }
148 return false
149 }
150
151 // LiveWarningMessage is printed to stderr before CLI --live starts MCP.
152 func LiveWarningMessage() string {
153 return strings.TrimSpace(`warning: --live will start third-party MCP servers in an isolated process.
154 They may access the network and receive configured environment variables and headers.
155 Tools are not registered into the agent registry. Startup stats/schema cache writes are disabled.
156 Host is always closed after the probe.`)
157 }
158
158 lines GO