Module: MCP::Client::McpParamHeaders
- Defined in:
- lib/mcp/client/mcp_param_headers.rb
Overview
The custom-header half of SEP-2243 (MCP 2026-07-28): scanning a tool's inputSchema for
x-mcp-header declarations and encoding tools/call argument values into Mcp-Param-{Name} HTTP headers,
with the =?base64?...?= sentinel for values that cannot ride as plain ASCII field values.
Mirrors the TypeScript SDK's mcpParamHeaders codec; the standard-header half (Mcp-Method, Mcp-Name)
lives with the transport.
Constant Summary collapse
- HEADER_PREFIX =
The fixed prefix every custom-parameter header carries.
"Mcp-Param-"- X_MCP_HEADER_KEY =
The schema-extension property name a tool's
inputSchemacarries. "x-mcp-header"- RFC9110_TOKEN =
RFC 9110 Section 5.1
tokensyntax (1*tchar): rejects empty names, spaces, control characters (including CR/LF), and the HTTP delimiters. /\A[!#$%&'*+\-.^_`|~0-9A-Za-z]+\z/.freeze
- PERMITTED_TYPES =
The spec text admits
string,integer, andboolean.numberis also accepted because the published conformance referee annotatestype: "number"parameters and expects them mirrored; the TypeScript SDK makes the same accommodation. ["string", "integer", "boolean", "number"].freeze
- NON_REACHABLE_SUBSCHEMA_KEYWORDS =
JSON Schema keywords the SEP-2243 static-reachability constraint excludes from the
properties-only chain. Anx-mcp-headerunder any of these invalidates the tool definition rather than being silently ignored. [ "items", "prefixItems", "contains", "additionalProperties", "unevaluatedProperties", "unevaluatedItems", "propertyNames", "patternProperties", "dependentSchemas", "oneOf", "anyOf", "allOf", "not", "if", "then", "else", "$defs", "definitions", ].freeze
- OBJECT_VALUED_SUBSCHEMA_KEYWORDS =
Keywords whose value maps names to subschemas rather than being one subschema or a list of them.
["patternProperties", "dependentSchemas", "$defs", "definitions"].freeze
- MAX_SAFE_INTEGER =
Integers beyond 2**53 - 1 lose precision in JSON number interchange, so they are not mirrored; the TypeScript SDK refuses unsafe integers the same way.
(2**53) - 1
- BASE64_SENTINEL_PREFIX =
"=?base64?"- BASE64_SENTINEL_SUFFIX =
"?="
Class Method Summary collapse
-
.build(declarations, arguments) ⇒ Object
Builds the
Mcp-Param-{Name}headers for onetools/callfrom the scanned declarations and the call'sarguments. -
.encode_value(value) ⇒ Object
Encodes a header value per the spec's value-encoding rules: a safe plain-ASCII field value passes through unchanged, everything else is wrapped as
=?base64?{base64-of-UTF-8}?=. -
.primitive_to_string(value) ⇒ Object
Converts a primitive argument to its header string per the spec's type-conversion rules: strings pass through, booleans become lowercase
"true"/"false", and numbers become their decimal string. -
.scan(input_schema) ⇒ Object
Scans a tool's
inputSchemaforx-mcp-headerdeclarations and validates every constraint the spec places on them: RFC 9110 token names, case-insensitive uniqueness, primitive-typed declaring properties, and static reachability through a chain ofpropertieskeys only.
Class Method Details
.build(declarations, arguments) ⇒ Object
Builds the Mcp-Param-{Name} headers for one tools/call from the scanned declarations and
the call's arguments. A null or absent value omits its header (the spec's MUST-omit rows);
a non-primitive or non-representable value is omitted rather than emitted malformed.
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/mcp/client/mcp_param_headers.rb', line 78 def build(declarations, arguments) declarations.each_with_object({}) do |declaration, headers| value = value_at_path(arguments, declaration[:path]) next if value.nil? string_value = primitive_to_string(value) next unless string_value encoded = begin encode_value(string_value) rescue EncodingError # A string that cannot be represented as UTF-8 (e.g. binary data) has no header # representation; omit it like the other non-representable values. next end headers["#{HEADER_PREFIX}#{declaration[:header_name]}"] = encoded end end |
.encode_value(value) ⇒ Object
Encodes a header value per the spec's value-encoding rules: a safe plain-ASCII field value
passes through unchanged, everything else is wrapped as =?base64?{base64-of-UTF-8}?=.
122 123 124 125 126 |
# File 'lib/mcp/client/mcp_param_headers.rb', line 122 def encode_value(value) return value unless needs_base64?(value) "#{BASE64_SENTINEL_PREFIX}#{[value.encode(Encoding::UTF_8)].pack("m0")}#{BASE64_SENTINEL_SUFFIX}" end |
.primitive_to_string(value) ⇒ Object
Converts a primitive argument to its header string per the spec's type-conversion rules:
strings pass through, booleans become lowercase "true" / "false", and numbers become
their decimal string. nil means "not representable: do not emit a header".
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/mcp/client/mcp_param_headers.rb', line 101 def primitive_to_string(value) case value when String value when true "true" when false "false" when Integer value.abs <= MAX_SAFE_INTEGER ? value.to_s : nil when Float return unless value.finite? # JSON has one number type: an integral float serializes without the fractional part, # matching the `String(42.0)` the JavaScript reference emits. value == value.truncate ? value.truncate.to_s : value.to_s end end |
.scan(input_schema) ⇒ Object
Scans a tool's inputSchema for x-mcp-header declarations and validates every constraint
the spec places on them: RFC 9110 token names, case-insensitive uniqueness, primitive-typed
declaring properties, and static reachability through a chain of properties keys only.
Returns { valid: true, declarations: [...] } with each declaration { path:, header_name:, type: },
or { valid: false, reason: "..." } on the first violation.
68 69 70 71 72 73 |
# File 'lib/mcp/client/mcp_param_headers.rb', line 68 def scan(input_schema) declarations = [] fault = visit(input_schema, [], true, declarations, {}) fault ? { valid: false, reason: fault } : { valid: true, declarations: declarations } end |