Skip to content

Stainless SDK provenance classifier (v0.8.3+)

agent_airlock.sdk_provenance.classify_sdk_lineage is a pure-function classifier that scans an MCP server's User-Agent header and response-body head for markers indicating the server was generated by a specific SDK toolchain.

What this is NOT

This is not a runtime probe, proxy, or sidecar. agent-airlock's @Airlock decorator wraps a Python tool function the agent calls; it does NOT intercept outbound HTTP, so the decorator never sees a server's response headers on its own. The decorator-in-process model is a deliberate anti-pivot choice (in-process decorator, not a proxy/sidecar).

This module is a building block. A tool function that itself calls out to an MCP server can pass the response's UA / first bytes through this classifier and tag the result onto the trajectory event.

Anchor

Anthropic announced the acquisition of Stainless on 2026-05-13 and the wind-down of hosted Stainless products, including the SDK generator. Operators wanting visibility into which MCP servers in their inventory were generated by the deprecated Stainless toolchain can call this classifier from their own audit hooks.

Quickstart

from agent_airlock import (
    SDKLineage,
    classify_sdk_lineage,
)

# Inside YOUR tool function (agent-airlock does not auto-probe):
response = http_client.get("https://my-mcp-server.example/info")
match = classify_sdk_lineage(
    user_agent=response.request.headers.get("User-Agent"),
    response_body_head=response.text[:4096],
)
if match.lineage == SDKLineage.STAINLESS:
    logger.info(
        "mcp_server_lineage_stainless",
        match_source=match.match_source,
        matched_pattern=match.matched_pattern,
    )
# Tag the trajectory event with the lineage label regardless of outcome.
audit_event["sdk_lineage"] = match.lineage.value

Match dataclass

@dataclass(frozen=True)
class SDKLineageMatch:
    lineage: SDKLineage              # STAINLESS | UNKNOWN
    match_source: str | None         # "user_agent" | "response_body" | None
    matched_pattern: str | None      # the literal substring that fired
    detail: str

A UA match short-circuits — the body is not scanned when the UA already names Stainless. Matching is case-insensitive substring on both surfaces.

Default markers

DEFAULT_STAINLESS_UA_PATTERNS = frozenset({
    "stainless",
    "stainless-sdk",
    "stainless-node",
    "stainless-python",
})

DEFAULT_STAINLESS_BODY_MARKERS = (
    "auto-generated by Stainless",
    "Generated by Stainless",
    "@stainless-generated",
    "stainless-codegen",
)

Only the first 4 KB of the response body is scanned. Operators who want deeper scans should ship the response head through some other tool — keeping the agent-airlock surface lightweight is deliberate.

Operator overrides

match = classify_sdk_lineage(
    user_agent=ua,
    response_body_head=head,
    extra_ua_patterns=frozenset({"acme-internal-gen"}),
    extra_body_markers=("AcmeCodeGen-v3",),
)

A match on an operator-added pattern still yields SDKLineage.STAINLESS because there is currently only one lineage label. Future versions may add additional lineage labels (e.g. OPENAPI_GENERATOR, STAINLESS_INHERITED) when operator demand justifies the taxonomy expansion.

Factory

from agent_airlock.policy_presets import stainless_provenance_probe_defaults

cfg = stainless_provenance_probe_defaults()
# {
#   'preset_id': 'stainless_provenance_probe_2026_05_19',
#   'severity': 'info',
#   'default_action': 'tag_only',
#   'advisory_url': 'https://www.anthropic.com/news/anthropic-acquires-stainless',
#   'ua_patterns': frozenset({'stainless', 'stainless-sdk', ...}),
#   'body_markers': ('auto-generated by Stainless', ...),
#   ...
# }

default_action is "tag_only" — this preset never refuses or blocks. It is visibility-only.

Honest scope

  • No automatic probing. agent-airlock does not intercept HTTP on its own. Operators wire this classifier into their own audit hooks.
  • One lineage label today. Only Stainless is recognised. The enum can be extended.
  • Substring matching, not signature verification. A determined party could spoof the markers. This is a visibility primitive, not a tamper-resistant attestation.
  • 4 KB scan window. A banner past byte 4096 of a response is missed by design.