Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

103 lines
3.8 KiB
Go

package mcp
import (
"context"
"strings"
"github.com/mark3labs/mcp-go/mcp"
)
// hostContext is a runtime, per-host adaptation of the served tool
// surface, resolved from the MCP initialize clientInfo.name. It is the
// serve-time counterpart of the install-time agent adapters: it can hide
// tools the host duplicates, override individual tool descriptions, and
// carry a host-specific guidance fragment surfaced via tool_profile.
type hostContext struct {
name string // canonical host name ("" = no context)
matches []string // lowercase substrings of clientInfo.name that select this context
instruction string // host-specific guidance fragment
excluded map[string]bool // tools removed from this host's tools/list
descOverride map[string]string // per-tool description replacements
}
// empty reports whether this context applies no adaptation at all.
func (h hostContext) empty() bool {
return h.name == "" && len(h.excluded) == 0 && len(h.descOverride) == 0
}
// apply returns tools with this context's exclusions removed and its
// description overrides applied. The input slice and its elements are
// not mutated.
func (h hostContext) apply(tools []mcp.Tool) []mcp.Tool {
if len(h.excluded) == 0 && len(h.descOverride) == 0 {
return tools
}
out := make([]mcp.Tool, 0, len(tools))
for _, t := range tools {
if h.excluded[t.Name] {
continue
}
if ov, ok := h.descOverride[t.Name]; ok {
t.Description = ov
}
out = append(out, t)
}
return out
}
// editorHostInstruction is shared by the IDE-extension hosts.
const editorHostInstruction = "You are driving Gortex from an editor extension. Push unsaved buffers " +
"with overlay_push so graph queries see your in-progress edits before they reach disk, and use " +
"preview_edit / simulate_chain to evaluate a change without writing it."
// hostContexts is the runtime registry of per-host adaptations, matched
// against the MCP initialize clientInfo.name. Order matters — the first
// matching entry wins, so more specific hosts come first.
var hostContexts = []hostContext{
{
name: "claude-code",
matches: []string{"claude"},
instruction: "Gortex runs here with PreToolUse hooks that redirect Read / Grep / Glob to graph " +
"tools. Begin every task with smart_context, prefer get_symbol_source over reading whole " +
"files, and edit through edit_file / edit_symbol.",
},
{name: "cursor", matches: []string{"cursor"}, instruction: editorHostInstruction},
{name: "vscode", matches: []string{"vscode", "visual studio"}, instruction: editorHostInstruction},
{name: "zed", matches: []string{"zed"}, instruction: editorHostInstruction},
{name: "windsurf", matches: []string{"windsurf"}, instruction: editorHostInstruction},
{name: "jetbrains", matches: []string{"jetbrains", "intellij"}, instruction: editorHostInstruction},
{
name: "codex",
matches: []string{"codex"},
instruction: "Gortex is available as an MCP server. Use search_symbols and smart_context to " +
"locate code before editing, and verify changes with check_guards and get_test_targets.",
},
}
// resolveHostContext returns the hostContext matching clientName, or the
// zero context (no adaptation) when the host is unknown or unidentified.
func resolveHostContext(clientName string) hostContext {
name := strings.ToLower(strings.TrimSpace(clientName))
if name == "" {
return hostContext{}
}
for _, hc := range hostContexts {
for _, m := range hc.matches {
if strings.Contains(name, m) {
return hc
}
}
}
return hostContext{}
}
// sessionHostContext resolves the per-host context for the request's
// session from the MCP client name captured at initialize time.
func (s *Server) sessionHostContext(ctx context.Context) hostContext {
sess := s.sessionFor(ctx)
if sess == nil {
return hostContext{}
}
return resolveHostContext(sess.snapshotClientName())
}