Documentation
¶
Overview ¶
Package config loads mcp-linux-over-ssh's configuration.
The same YAML file feeds two parsers: generic-go-mcp's own config.Load reads the server: and logging: blocks, and this package reads the ssh: block, plus a couple of fields the library's logging: block doesn't have room for (see LoggingConfig). The library's config.Config has no extension point, so a second unmarshal of the same file is the least surprising way to carry our own settings.
Index ¶
Constants ¶
const ( DefaultLoginTimeout = 60 * time.Second DefaultCommandTimeout = 300 * time.Second DefaultRecoveryTimeout = 15 * time.Second DefaultTerm = "dumb" DefaultCols = 500 DefaultRows = 24 DefaultMaxOutputBytes = 1 << 20 // 1 MiB DefaultTranscriptBytes = 256 << 10 DefaultMaxSessions = 8 // DefaultDisableFlagPollInterval is how quickly an open, quiet session notices the // disable flag file appearing. Only meaningful when ssh.disable_flag_file is set. Five // seconds is a stat every five seconds against a promptness an operator reaching for a // kill switch will accept; the refusal of *new* work is immediate regardless. DefaultDisableFlagPollInterval = 5 * time.Second )
Defaults applied to any field the file leaves unset.
const DefaultParameterPattern = `^[A-Za-z0-9._@:/+-]{1,256}$`
DefaultParameterPattern is what an omitted pattern: means. It admits hostnames, usernames, IPs, paths and ports, and nothing that carries meaning to a shell.
The default is deliberately restrictive rather than permissive. ssh.command is rendered into a string handed to `sh -c` on *this* host, so a value carrying shell metacharacters is local command execution on the MCP server — a materially worse outcome than run_bash's deliberate execution on the far end, and the values now come from an MCP client rather than from the operator who wrote the config. An operator who genuinely needs looser values widens the pattern explicitly, and reaches for shquote in the template to go with it.
const ExampleConfigURL = "https://github.com/spirilis/mcp-linux-over-ssh/blob/main/config.yaml.example"
ExampleConfigURL is the fully commented reference configuration.
Spelled out as a URL rather than as the bare filename "config.yaml.example", because the people most likely to run --help-config are the ones with no repository checkout: someone who ran `go install`, or who is pointing an MCP client at a container image. A filename they cannot locate is not a reference.
Variables ¶
var DefaultInitCommands = []string{"stty -echo", "unset PROMPT_COMMAND"}
DefaultInitCommands quiet the remote shell down before we start scraping it. `stty -echo` is the clean fix for command echo on a PTY; PROMPT_COMMAND can emit arbitrary text between the output and the prompt.
Functions ¶
func HelpConfig ¶ added in v0.2.0
func HelpConfig() string
HelpConfig renders --help-config's output.
Types ¶
type Config ¶
type Config struct {
Lib *libconfig.Config
SSH SSHConfig
Logging LoggingConfig
// Renderer turns start_session's arguments into one session's command and prompt
// regex. Built by Load so a template that references an undeclared parameter is a
// startup failure rather than a first-call one.
Renderer *Renderer
}
Config is the whole file: the library's half plus ours.
func (*Config) LegacyCompatEnabled ¶ added in v0.2.0
LegacyCompatEnabled reports whether the legacy (2025-11-25 and earlier) MCP compatibility overlay (compat.Overlay) is active. On by default — the inverse of the library's own default — since most MCP clients as of this writing (Claude Desktop, most IDEs) do not yet speak 2026-07-28, and this server exists to keep working with the clients already deployed against it. Set server.legacy_compat.enabled: false once every client this server serves has moved off the legacy revisions.
func (*Config) LegacyCompatSessionTTL ¶ added in v0.2.0
LegacyCompatSessionTTL is server.legacy_compat.session_ttl, parsed. Load has already validated it parses; an empty string returns zero, which lets compat.Overlay apply its own 30-minute default.
type Example ¶ added in v0.2.0
type Example struct {
// Name is the heading, e.g. "No parameters".
Name string
// Description is one line on when to reach for this shape.
Description string
// YAML is a complete, loadable configuration.
YAML string
// Params is how many parameters the example declares, and therefore how many
// arguments its start_session takes. Asserted by the test.
Params int
}
Example is one annotated configuration shown by --help-config.
These are not prose. Every one is loaded and rendered by TestExamplesAllLoad, so an example that stops parsing — or stops matching what the parser actually accepts — fails the build rather than quietly misleading whoever copies it. That is the whole reason this lives in the config package instead of as a string in main.
type LoggingConfig ¶
type LoggingConfig struct {
// File redirects logging from stderr (the default) to this path, opened for append
// and created if missing. Empty means stderr, unchanged from before this field
// existed.
File string `yaml:"file"`
}
LoggingConfig carries the logging: fields the library's own LoggingConfig has no room for. It is unmarshalled from the same logging: block as the library's level/format — yaml.Unmarshal ignores keys a struct doesn't declare, so the two parsers coexist without either seeing an "unknown field" error.
type ParameterConfig ¶ added in v0.2.0
type ParameterConfig struct {
// Parameter is the template variable name, referenced as {{.Parameter}}. It must be a
// valid Go template field identifier.
Parameter string `yaml:"parameter"`
// Description is surfaced to the MCP client verbatim, as the description of the
// matching property on start_session's generated input schema. It is the only thing
// telling the model what to put here, so it is worth writing well.
Description string `yaml:"description"`
// Pattern is a Go (RE2) regex every supplied value must match in full. Empty means
// DefaultParameterPattern. See NewRenderer for why this is not optional in spirit.
Pattern string `yaml:"pattern"`
}
ParameterConfig declares one caller-supplied value that ssh.command and ssh.prompt_regex may interpolate.
type Renderer ¶ added in v0.2.0
type Renderer struct {
// contains filtered or unexported fields
}
Renderer turns a set of caller-supplied parameter values into the concrete command line and prompt regex for one session. It is built once at startup and used concurrently by every start_session call, so it holds nothing mutable.
func NewRenderer ¶ added in v0.2.0
NewRenderer validates the parameter declarations and the two templates against each other, so a config that could only fail at start_session time fails at startup instead.
func (*Renderer) Parameters ¶ added in v0.2.0
func (r *Renderer) Parameters() []ParameterConfig
Parameters returns the declared parameters, each with its effective pattern (the default substituted where the config omitted one).
func (*Renderer) Parametric ¶ added in v0.2.0
Parametric reports whether any parameter is declared. A config with none still renders — the templates are simply constant — which is what keeps a single-target deployment from having to care that any of this exists.
func (*Renderer) Render ¶ added in v0.2.0
Render validates values and produces one session's command line and prompt regex.
Every declared parameter must be present and match its pattern, and no undeclared key is accepted: silently ignoring an unknown key would let a model believe it had selected a target it had not.
type SSHConfig ¶
type SSHConfig struct {
// Command is the shell command line that opens a session, run via `sh -c`. It is a Go
// text/template rendered per session against the values start_session was called with;
// with no ssh.parameters declared it is simply a constant. Each rendered command is
// executed exactly once, for the life of the session it opened.
//
// SECURITY: this string reaches `sh -c` on the host running this server, so what a
// parameter is permitted to interpolate into it is the difference between choosing a
// target and choosing a command. See ParameterConfig.Pattern.
Command string `yaml:"command"`
// PromptRegex matches the remote shell's prompt. It frames the initial login and
// backstops recovery; per-command framing uses the sentinel when available. Also a
// template, so a prompt that embeds the target's own hostname or username can be
// matched exactly.
PromptRegex string `yaml:"prompt_regex"`
// Parameters declares the values start_session accepts and interpolates into Command
// and PromptRegex. Empty means the two templates are constants and start_session takes
// no arguments.
Parameters []ParameterConfig `yaml:"parameters"`
// MaxSessions caps how many live sessions may exist at once. Sessions that have died
// do not count, so a failing target cannot wedge the server by filling the cap.
MaxSessions int `yaml:"max_sessions"`
// IdleTimeout closes sessions that have gone this long without running a command.
// Zero (the default) never reaps: on a PAM/PSM bastion, tearing down a privileged
// session someone is still reasoning about is worse than keeping it, and a stdio
// server's sessions are already bounded by the life of its client.
IdleTimeout time.Duration `yaml:"idle_timeout"`
// DisableFlagFile is an administrative kill switch: while a file exists at this path,
// start_session and run_bash refuse with an error saying so, and sessions already open
// are terminated as soon as they are not mid-command. Removing the file restores normal
// service with no restart. Empty (the default) means no such check exists at all.
//
// Only the file's existence matters; its contents are never read. A relative path is
// resolved against the working directory at startup — see session.NewFlagFileGate for
// why that is done once rather than per check.
DisableFlagFile string `yaml:"disable_flag_file"`
// DisableFlagPollInterval is how often the flag file is re-checked for the purpose of
// terminating sessions that are already open. It does not delay the refusal of new work:
// start_session and run_bash stat the file themselves, so they can never act on a stale
// answer. This bounds only how long an open, quiet session outlives the flag appearing.
DisableFlagPollInterval time.Duration `yaml:"disable_flag_poll_interval"`
LoginTimeout time.Duration `yaml:"login_timeout"`
CommandTimeout time.Duration `yaml:"command_timeout"`
RecoveryTimeout time.Duration `yaml:"recovery_timeout"`
Term string `yaml:"term"`
Cols uint16 `yaml:"cols"`
Rows uint16 `yaml:"rows"`
// InitCommands run once after login, before the session is declared ready.
InitCommands []string `yaml:"init_commands"`
// UseSentinel enables per-command end markers carrying $?. Pointer so an explicit
// `false` is distinguishable from an omitted key (default true).
UseSentinel *bool `yaml:"use_sentinel"`
MaxOutputBytes int `yaml:"max_output_bytes"`
TranscriptBytes int `yaml:"transcript_bytes"`
// AllowReconnect permits re-running Command once if the session dies. Off by
// default: the whole premise of this server is that the command cannot simply be
// re-run at will.
AllowReconnect bool `yaml:"allow_reconnect"`
// RequireConfirmation gates every execution behind an MRTR elicitation. Pointer so
// an explicit `false` is distinguishable from an omitted key. Default is true unless
// legacy MCP compatibility is on, in which case it is false — see
// RequireConfirmationValue.
RequireConfirmation *bool `yaml:"require_confirmation"`
}
SSHConfig describes the shell sessions this server drives. One session is one SSH command, spawned on demand by start_session and run exactly once; several may be live at a time, each independent of the others.
func (*SSHConfig) RequireConfirmationValue ¶
RequireConfirmationValue reports whether every execution needs an MRTR confirmation. An explicit require_confirmation in the config always wins. Left unset, the default tracks legacyCompatEnabled: MRTR (mcp.ToolRequest.NeedInput) has no legacy-MCP equivalent — a pre-2026-07-28 client gets a visible isError refusal on every run_bash call instead of the confirmation round trip (see compat.Overlay's result downgrading) — so confirmation defaults off while compat is on, and on otherwise, same as before this field existed.
func (*SSHConfig) UseSentinelValue ¶
UseSentinelValue reports whether per-command sentinel markers are enabled.