a06f331eb8
install-script / powershell-syntax (push) Waiting to run
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
install-script / install (macos-14) (push) Waiting to run
install-script / install (ubuntu-latest) (push) Waiting to run
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
135 lines
5.2 KiB
Go
135 lines
5.2 KiB
Go
package mcp
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/mark3labs/mcp-go/mcp"
|
|
)
|
|
|
|
// RemoteOverrideSink lets the per-session proxy-toggle tools push an
|
|
// enable/disable override for a remote into the daemon's session state,
|
|
// and read the roster's effective state. It is implemented by the daemon
|
|
// (which owns the session registry + the live router) and wired via
|
|
// SetRemoteOverrideSink; nil in embedded mode (no per-connection daemon
|
|
// session), where the tools return a "use the global CLI" message.
|
|
type RemoteOverrideSink interface {
|
|
// SetRemoteOverride records a session-scoped enable/disable for a
|
|
// remote slug. Errors on an unknown slug or a missing session.
|
|
SetRemoteOverride(sessionID, slug string, enabled bool) error
|
|
// ClearRemoteOverride drops a session override so the remote reverts
|
|
// to its global enabled state.
|
|
ClearRemoteOverride(sessionID, slug string) error
|
|
// RemoteRosterStatus returns each roster remote's global + effective
|
|
// enabled state for the given session.
|
|
RemoteRosterStatus(sessionID string) ([]RemoteRosterStatus, error)
|
|
}
|
|
|
|
// RemoteRosterStatus is one roster remote's enabled-state view for a
|
|
// session: the persistent global state, the session override (nil when
|
|
// none), and the resulting effective state.
|
|
type RemoteRosterStatus struct {
|
|
Slug string `json:"slug"`
|
|
GlobalEnabled bool `json:"global_enabled"`
|
|
SessionOverride *bool `json:"session_override,omitempty"`
|
|
Effective bool `json:"effective"`
|
|
}
|
|
|
|
// SetRemoteOverrideSink wires the daemon-backed remote-override bridge
|
|
// and registers the session proxy-toggle tools once. Idempotent.
|
|
func (s *Server) SetRemoteOverrideSink(sink RemoteOverrideSink) {
|
|
s.remoteOverrides = sink
|
|
if sink == nil {
|
|
return
|
|
}
|
|
s.registerProxyToolsOnce.Do(func() { s.registerProxyTools() })
|
|
}
|
|
|
|
func (s *Server) registerProxyTools() {
|
|
s.mcpServer.AddTool(
|
|
mcp.NewTool("proxy_enable",
|
|
mcp.WithDescription("Enable federation/proxy to a remote Gortex daemon FOR THIS MCP SESSION ONLY. Overrides the global enabled state for the session's lifetime and auto-reverts on disconnect. For a persistent global change use `gortex proxy on <slug>`."),
|
|
mcp.WithString("slug", mcp.Required(), mcp.Description("Roster slug of the remote to enable for this session.")),
|
|
),
|
|
s.handleProxyEnable,
|
|
)
|
|
s.mcpServer.AddTool(
|
|
mcp.NewTool("proxy_disable",
|
|
mcp.WithDescription("Disable federation/proxy to a remote Gortex daemon FOR THIS MCP SESSION ONLY. Beats the global enabled state for the session's lifetime; queries from this session skip the remote. Auto-reverts on disconnect. For a persistent global change use `gortex proxy off <slug>`."),
|
|
mcp.WithString("slug", mcp.Required(), mcp.Description("Roster slug of the remote to disable for this session.")),
|
|
),
|
|
s.handleProxyDisable,
|
|
)
|
|
s.mcpServer.AddTool(
|
|
mcp.NewTool("proxy_status",
|
|
mcp.WithDescription("List every roster remote with its global enabled state, this session's override (if any), and the resulting effective enabled state."),
|
|
),
|
|
s.handleProxyStatus,
|
|
)
|
|
}
|
|
|
|
func (s *Server) handleProxyEnable(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
return s.handleProxyToggle(ctx, req, true)
|
|
}
|
|
|
|
func (s *Server) handleProxyDisable(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
return s.handleProxyToggle(ctx, req, false)
|
|
}
|
|
|
|
func (s *Server) handleProxyToggle(ctx context.Context, req mcp.CallToolRequest, enabled bool) (*mcp.CallToolResult, error) {
|
|
slug, err := req.RequireString("slug")
|
|
if err != nil {
|
|
return mcp.NewToolResultError("slug is required"), nil
|
|
}
|
|
if s.remoteOverrides == nil {
|
|
return s.sessionToggleUnavailable(), nil
|
|
}
|
|
sid := SessionIDFromContext(ctx)
|
|
if sid == "" {
|
|
return s.sessionToggleUnavailable(), nil
|
|
}
|
|
if err := s.remoteOverrides.SetRemoteOverride(sid, slug, enabled); err != nil {
|
|
return NewStructuredErrorResult(StructuredError{
|
|
ErrorCode: ErrCodeInvalidArgument,
|
|
Message: err.Error(),
|
|
Data: map[string]any{"slug": slug},
|
|
}), nil
|
|
}
|
|
state := "disabled"
|
|
if enabled {
|
|
state = "enabled"
|
|
}
|
|
return s.respondJSONOrTOON(ctx, req, map[string]any{
|
|
"slug": slug,
|
|
"session_state": state,
|
|
"note": "session-scoped — auto-reverts on disconnect. Use `gortex proxy on/off` for a persistent global change.",
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleProxyStatus(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
if s.remoteOverrides == nil {
|
|
return s.respondJSONOrTOON(ctx, req, map[string]any{
|
|
"remotes": []any{},
|
|
"note": "no daemon-backed session; inspect remotes with `gortex proxy status`.",
|
|
})
|
|
}
|
|
rows, err := s.remoteOverrides.RemoteRosterStatus(SessionIDFromContext(ctx))
|
|
if err != nil {
|
|
return NewStructuredErrorResult(StructuredError{
|
|
ErrorCode: ErrCodeInvalidArgument,
|
|
Message: err.Error(),
|
|
}), nil
|
|
}
|
|
if rows == nil {
|
|
rows = []RemoteRosterStatus{}
|
|
}
|
|
return s.respondJSONOrTOON(ctx, req, map[string]any{"remotes": rows})
|
|
}
|
|
|
|
func (s *Server) sessionToggleUnavailable() *mcp.CallToolResult {
|
|
return NewStructuredErrorResult(StructuredError{
|
|
ErrorCode: ErrCodeToolBlockedByMode,
|
|
Message: "session proxy toggle requires a daemon-backed MCP session; use " +
|
|
"`gortex proxy on/off <slug>` for global control",
|
|
})
|
|
}
|