pooled-http-session-content-type-non-idempotent

Pattern: Pooled HTTP sessions require explicit Content-Type on JSON bodies

When an MCP server uses a session-pooling HTTP client (requests.Session, httpx.Client, aiohttp.ClientSession, simple_salesforce’s connection object, etc.) to talk to upstream APIs, never rely on the requests library’s auto-detection of Content-Type from the json= kwarg. The auto-detection is non-idempotent under session reuse — the header gets set on first call but subsequent calls in the same session may inherit stale headers (or none) from prior calls.

Symptom

Upstream API returns intermittent INVALID_TYPE: Content-Type header must be {1} (Salesforce Tooling API), or 415 Unsupported Media Type, or silent body-parse failures, on a fraction of calls — usually the second or later call in a session.

Discovered in

salesforce-mcp v7.0.1 (2026-04-29). sf_run_apex_tests calling conn.restful("tooling/runTestsAsynchronous", method="POST", json=body) failed intermittently. sf_run_relevant_tests — which already passed headers={"Content-Type": "application/json"} explicitly — never failed.

Fix

Always pass an explicit Content-Type header on every JSON-body POST/PATCH:

JSON_HEADERS = {"Content-Type": "application/json"}
 
result = conn.restful(
    "endpoint",
    method="POST",
    headers=JSON_HEADERS,  # <-- mandatory, not optional
    json=body,
)

Centralize in core/tooling_helper.py (or equivalent) so every call site gets the same constant. Banning json= without headers= via a lint/test rule is the long-term safety net.

Generalization

Applies to ANY MCP server using a session-pooling HTTP client:

  • simple_salesforce (Salesforce)
  • Any custom requests.Session usage
  • httpx.Client / AsyncClient with reuse
  • aiohttp.ClientSession with reuse

Does NOT apply when each call creates a fresh client (rare in MCP servers — wasteful).

  • MetadataContainer.Name has a 32-char hard limit — variable-length name templates overflow for long identifiers
  • ApexPage/ApexComponent meta XML requires <label>, NOT <status> (the legacy one-size template was invalid for half the metadata catalog)

These three together constitute the salesforce-mcp v7.0.1 fix bundle.