Implementation for SEP-2243 Http Standardization (#1553)

Co-authored-by: Tarek Mahmoud Sayed <tarekms@microsoft.com>
Co-authored-by: Tarek Mahmoud Sayed <tarekms@ntdev.microsoft.com>
Co-authored-by: Tarek Mahmoud Sayed <10833894+tarekgh@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Mike Kistler 2026-05-14 17:39:37 -05:00 • committed by GitHub
commit b8c4d951ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -0,0 +1,147 @@
using System.Net.Http.Headers;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol;
namespace ModelContextProtocol.Client;
/// <summary>
/// Extracts parameter values from tool call arguments and adds them as HTTP headers
/// based on <c>x-mcp-header</c> schema extensions.
/// </summary>
internal static class McpHeaderExtractor
{
private const string XMcpHeaderProperty = "x-mcp-header";
/// <summary>
/// Adds custom parameter headers to an HTTP request based on a tool's schema extensions.
/// </summary>
/// <param name="headers">The HTTP request headers to add to.</param>
/// <param name="tool">The tool definition containing the input schema with x-mcp-header annotations.</param>
/// <param name="arguments">The arguments being passed to the tool call.</param>
public static void AddParameterHeaders(
HttpRequestHeaders headers,
Tool tool,
JsonElement? arguments)
{
if (!arguments.HasValue || arguments.Value.ValueKind != JsonValueKind.Object)
{
return;
}
if (tool.InputSchema.ValueKind != JsonValueKind.Object ||
!tool.InputSchema.TryGetProperty("properties", out var properties) ||
properties.ValueKind != JsonValueKind.Object)
{
return;
}
foreach (var property in properties.EnumerateObject())
{
if (property.Value.ValueKind != JsonValueKind.Object ||
!property.Value.TryGetProperty(XMcpHeaderProperty, out var headerNameElement))
{
continue;
}
var headerName = headerNameElement.GetString();
if (string.IsNullOrEmpty(headerName))
{
continue;
}
// Look for the corresponding argument value
if (!arguments.Value.TryGetProperty(property.Name, out var argValue))
{
continue;
}
// Null values → omit header per SEP
if (argValue.ValueKind == JsonValueKind.Null)
{
continue;
}
var headerValue = McpHeaderEncoder.ConvertToHeaderValue(argValue);
if (headerValue is not null)
{
headers.Add($"{McpHttpHeaders.ParamPrefix}{headerName}", headerValue);
}
}
}
/// <summary>
/// Validates a tool's <c>inputSchema</c> for valid <c>x-mcp-header</c> annotations.
/// Returns <see langword="true"/> if the tool is valid; <see langword="false"/> with a reason if it should be rejected.
/// </summary>
internal static bool ValidateToolSchema(Tool tool, out string? rejectionReason)
{
rejectionReason = null;
if (tool.InputSchema.ValueKind != JsonValueKind.Object ||
!tool.InputSchema.TryGetProperty("properties", out var properties) ||
properties.ValueKind != JsonValueKind.Object)
{
return true;
}
var headerNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var property in properties.EnumerateObject())
{
// Skip properties whose schema is not an object (e.g., boolean `true`/`false` schemas)
if (property.Value.ValueKind != JsonValueKind.Object ||
!property.Value.TryGetProperty(XMcpHeaderProperty, out var headerNameElement))
{
continue;
}
// x-mcp-header value must be a string
if (headerNameElement.ValueKind != JsonValueKind.String)
{
rejectionReason = $"Tool '{tool.Name}': x-mcp-header on property '{property.Name}' is not a string.";
return false;
}
var headerName = headerNameElement.GetString();
// MUST NOT be empty
if (string.IsNullOrEmpty(headerName))
{
rejectionReason = $"Tool '{tool.Name}': x-mcp-header on property '{property.Name}' is empty.";
return false;
}
// MUST contain only ASCII characters (0x21-0x7E) excluding space and colon
foreach (char c in headerName!)
{
if (c < 0x21 || c > 0x7E || c == ':')
{
rejectionReason = $"Tool '{tool.Name}': x-mcp-header '{headerName}' contains invalid character '{c}' (0x{(int)c:X2}).";
return false;
}
}
// MUST be case-insensitively unique
if (!headerNames.Add(headerName))
{
rejectionReason = $"Tool '{tool.Name}': duplicate x-mcp-header name '{headerName}' (case-insensitive).";
return false;
}
// MUST only be applied to primitive types (string, number, boolean)
if (property.Value.TryGetProperty("type", out var typeElement) &&
typeElement.ValueKind == JsonValueKind.String)
{
var typeName = typeElement.GetString();
if (typeName is not ("string" or "number" or "integer" or "boolean"))
{
rejectionReason = $"Tool '{tool.Name}': x-mcp-header on property '{property.Name}' has non-primitive type '{typeName}'.";
return false;
}
}
}
return true;
}
}