142 lines
3.8 KiB
Go
142 lines
3.8 KiB
Go
package plugin
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
codeGraphDaemonIdleTimeoutEnv = "CODEGRAPH_DAEMON_IDLE_TIMEOUT_MS"
|
|
// Keep CodeGraph's shared daemon enabled, but do not leave it holding
|
|
// watchers for the upstream default 300s after the last MCP client exits.
|
|
codeGraphDaemonIdleTimeoutDefaultMS = "5000"
|
|
)
|
|
|
|
// ApplyKnownOverrides fills compatibility hints for known MCP servers. These
|
|
// are runtime-only adjustments; they do not make a server built-in or change
|
|
// startup behavior.
|
|
func ApplyKnownOverrides(s Spec, workspaceRoot string) Spec {
|
|
if isCodeGraphSpecName(s.Name) {
|
|
s.ReadOnlyToolNames = mergeReadOnlyToolNames(s.ReadOnlyToolNames, codeGraphReadOnlyToolNames())
|
|
if isStdioSpecType(s.Type) {
|
|
if s.Dir == "" {
|
|
s.Dir = strings.TrimSpace(workspaceRoot)
|
|
}
|
|
s.Env = mergeDefaultEnv(s.Env, codeGraphDaemonIdleTimeoutEnv, codeGraphDaemonIdleTimeoutDefaultMS)
|
|
}
|
|
// CodeGraph does full-tree indexing + file-watching; run it below normal
|
|
// scheduling priority so a background indexer can never starve the user's
|
|
// machine (#3797, #2992). The proc-level mechanism already exists but was
|
|
// never wired to the spec, so it stayed disabled.
|
|
s.LowPriority = true
|
|
}
|
|
if isCodebaseMemorySpec(s) {
|
|
if isStdioSpecType(s.Type) && s.Dir == "" {
|
|
s.Dir = strings.TrimSpace(workspaceRoot)
|
|
}
|
|
// codebase-memory-mcp detects the session root from its subprocess cwd
|
|
// during initialize, then optionally starts its own auto-index thread.
|
|
// Its initial full-tree indexing can be CPU-heavy; keep it out of the
|
|
// foreground scheduling lane just like CodeGraph.
|
|
s.LowPriority = true
|
|
}
|
|
return s
|
|
}
|
|
|
|
// ApplyKnownReadOnlyOverrides fills compatibility read-only hints for MCP
|
|
// servers whose read surfaces are stable but older runtimes may omit MCP
|
|
// annotations. It does not make the server built-in or change startup behavior.
|
|
func ApplyKnownReadOnlyOverrides(s Spec) Spec {
|
|
return ApplyKnownOverrides(s, "")
|
|
}
|
|
|
|
func isCodeGraphSpecName(name string) bool {
|
|
return strings.EqualFold(strings.TrimSpace(name), "codegraph")
|
|
}
|
|
|
|
func isCodebaseMemorySpec(s Spec) bool {
|
|
if isCodebaseMemoryID(s.Name) || isCodebaseMemoryCommand(s.Command) {
|
|
return true
|
|
}
|
|
for _, arg := range s.Args {
|
|
if isCodebaseMemoryID(arg) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isCodebaseMemoryCommand(command string) bool {
|
|
command = strings.TrimSpace(command)
|
|
if command == "" {
|
|
return false
|
|
}
|
|
command = strings.ReplaceAll(command, `\`, `/`)
|
|
return isCodebaseMemoryID(filepath.Base(command))
|
|
}
|
|
|
|
func isCodebaseMemoryID(raw string) bool {
|
|
id := strings.ToLower(strings.TrimSpace(raw))
|
|
id = strings.TrimSuffix(id, ".exe")
|
|
id = strings.TrimPrefix(id, "io.github.deusdata/")
|
|
if strings.HasPrefix(id, "codebase-memory-mcp@") {
|
|
return true
|
|
}
|
|
switch id {
|
|
case "codebase-memory-mcp", "codebase-memory":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isStdioSpecType(typ string) bool {
|
|
typ = strings.ToLower(strings.TrimSpace(typ))
|
|
return typ == "" || typ == "stdio"
|
|
}
|
|
|
|
func mergeReadOnlyToolNames(existing map[string]bool, extra map[string]bool) map[string]bool {
|
|
out := make(map[string]bool, len(existing)+len(extra))
|
|
for name, ok := range existing {
|
|
out[name] = ok
|
|
}
|
|
for name, ok := range extra {
|
|
if ok {
|
|
out[name] = true
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func mergeDefaultEnv(existing map[string]string, key, value string) map[string]string {
|
|
out := make(map[string]string, len(existing)+1)
|
|
for name, v := range existing {
|
|
out[name] = v
|
|
}
|
|
if _, ok := out[key]; !ok {
|
|
out[key] = value
|
|
}
|
|
return out
|
|
}
|
|
|
|
func codeGraphReadOnlyToolNames() map[string]bool {
|
|
base := []string{
|
|
"callees",
|
|
"callers",
|
|
"context",
|
|
"explore",
|
|
"files",
|
|
"impact",
|
|
"node",
|
|
"search",
|
|
"status",
|
|
"trace",
|
|
}
|
|
out := make(map[string]bool, len(base)*2)
|
|
for _, name := range base {
|
|
out[name] = true
|
|
out["codegraph_"+name] = true
|
|
}
|
|
return out
|
|
}
|