Documentation
¶
Overview ¶
Package gatewaycompat rewrites the human-readable text this server lists — tool, prompt, resource and resource-template descriptions and titles, and the description and title annotations embedded in tool schemas — according to operator-defined substitutions.
It exists because MCP gateways validate a server's catalog before admitting it, and their rules are the gateway operator's to choose: one production gateway (IBM mcp-context-forge before 0.7.0) refused any tool whose description contained a semicolon. This server keeps its own text clean of the characters known to be rejected (cmd/audit_gateway_chars gates that), but the next gateway rule is not this project's to predict. The substitution knob lets the operator comply with a rule the day they meet it, without waiting for a release.
The knob rewrites catalog metadata and nothing else: names, URIs, schema constraints (pattern, const, enum values, defaults) and tool-call payloads are never touched, because those are contract, not prose.
Index ¶
Constants ¶
const ( MaxSubstitutions = 32 MaxSubstitutionBytes = 256 )
MaxSubstitutions and MaxSubstitutionBytes bound the configuration, and growthFactor and growthAllowance bound its effect.
The knob is the only setting that writes operator-chosen prose into the channel a model reads as instructions, and it is an ordinary environment variable rather than a flag. Bounding it keeps a compliance tool a compliance tool: a gateway rule is satisfied by a handful of short rewrites, so a configuration that needs hundreds of rules, or a paragraph per rule, is not the use this exists for.
The two effect bounds exist because a length limit per rule does not bound the result. A rule replacing a single frequent letter with 200 bytes stays well inside every configuration limit and still multiplies the served catalog. So the ceiling is relative to the text being rewritten: output may not exceed growthFactor times the input, nor the input plus growthAllowance bytes, whichever is larger. The allowance is what keeps short titles rewritable.
const EnvVar = "GITLAB_MCP_DESCRIPTION_SUBSTITUTIONS"
EnvVar configures the substitutions in both stdio and HTTP modes. The value is a comma-separated list of old=new pairs applied in order to every listed description and title. A backslash escapes a literal comma, equals sign or backslash inside either half, so a semicolon can become a comma:
GITLAB_MCP_DESCRIPTION_SUBSTITUTIONS=';=\,'
Whitespace is significant: "; = " substitutes "; " (semicolon, space) with " " (a single space). An empty or unset value disables the middleware.
Variables ¶
This section is empty.
Functions ¶
func Apply ¶
func Apply(subs []Substitution, text string) string
Apply runs every substitution over text, in declared order, unless doing so would grow the text past the ceiling growthLimit sets — in which case text is returned as written.
The refusal is all-or-nothing. Applying the rules that fit would serve a third text nobody configured, and the unrewritten one is the text this project's own gateway-character audit vouches for.
Each rule's result length is computed before the rule runs, so an oversized string is never built.
func Middleware ¶
func Middleware(subs []Substitution) mcp.Middleware
Middleware returns a receiving middleware that applies subs to the four catalog listings a gateway validates: tools/list, prompts/list, resources/list and resources/templates/list. Every other method passes through untouched — a prompt's message content or a tool call's result is payload, and rewriting payload would change what the server does rather than how it introduces itself.
Results are cloned before modification: list results return pointers shared with the server's registries, so an in-place edit would corrupt the catalog for every other session of this process.
func RewriteSchemaProse ¶
RewriteSchemaProse walks a decoded JSON schema value, passes every prose string — the value of a description or title keyword, however deeply nested — to rewrite, stores what it returns, and reports whether anything changed. Everything that is not prose survives verbatim: names, patterns, and the data keywords (default, const, enum, examples), whose subtrees are never entered.
It is exported for cmd/audit_gateway_chars, which scans the same strings this package rewrites: sharing the walk is what keeps "what the audit checks" and "what the knob can fix" the same set by construction.
Types ¶
type Substitution ¶
type Substitution struct {
// Old is the literal text to replace. Never empty.
Old string
// New is the literal replacement. May be empty, which deletes Old.
New string
}
Substitution is one ordered old→new text replacement.
func FromEnv ¶
func FromEnv() ([]Substitution, error)
FromEnv parses EnvVar. An unset or empty variable is not an error: it returns nil, nil, and the caller installs nothing.
An active configuration is announced at WARN, once per process. A rewritten catalog is otherwise indistinguishable at runtime from an unrewritten one, which makes "the descriptions the model reads are not the ones this build ships" a fact with no local evidence.
func ParseSubstitutions ¶
func ParseSubstitutions(value string) ([]Substitution, error)
ParseSubstitutions parses a comma-separated list of old=new pairs.
Within a pair, the first unescaped equals sign separates old from new; later unescaped equals signs in new are literal. Backslash escapes a comma, an equals sign or a backslash; any other escape is an error, because a silently absorbed typo here would ship a substitution the operator did not write. An empty old is an error for the same reason: ReplaceAll with an empty pattern inserts new between every pair of characters.