Proposals · SEP-2817 · Draft

AI Invocation Audit Context in Request `_meta`

Standards Track · Created 2026-05-30 · Source

Imported from upstream. Proposed as modelcontextprotocol#2817 by @hangum on 2026-05-29, where it was still open on 2026-09-25. Accepted into omcp on its technical merit. The text is the author's; only the status, the sponsor field, and documentation links were changed on import.

Reviewed 2026-09-25 — not yet accepted. Held as a draft because no prototype a reviewer can run is cited. Acceptance needs a prototype a reviewer can run, with its setup and the data to run it; Final also needs conformance tests and the reference documentation chapter.

Abstract

This SEP proposes a small optional convention for carrying AI-generated invocation audit context in MCP request _meta.

When an AI host or client initiates an MCP request, the server often needs operational audit context that is not part of the tool-specific arguments:

Today, implementations that need this context must invent server-specific arguments such as user_query, invocation_reason, or transport-specific model metadata. That pollutes tool schemas, does not generalize across transports, and fragments audit logs across MCP servers.

This SEP defines one optional request _meta entry:

The value is an object that may contain invocationReason, model, userIntent, and turnId.

All four fields are optional and client-asserted. Servers may ignore, redact, transform, or store them according to local policy, and MAY echo only turnId back in response _meta for correlation. These fields are not authorization evidence.

Motivation

AI-initiated MCP requests are different from ordinary application requests because the immediate caller may be an AI host acting on behalf of a user. For audit, incident review, and security monitoring, operators often need to answer:

In practice, server implementations have already started solving this locally. TadpoleDBHub, for example, records a server-specific audit context using custom request arguments and transport metadata:

This works as an implementation-specific convention, but it has protocol-level drawbacks:

A standard _meta convention would let clients provide this audit context consistently without changing each tool schema.

Specification

Clients MAY include the following optional object in request params._meta for AI-initiated MCP requests:

_meta["io.modelcontextprotocol/aiInvocation"]?: {
  invocationReason?: {
    text: string;
  };
  model?: {
    name: string;
  };
  userIntent?: {
    text?: string;
    redacted?: boolean;
  };
  turnId?: string;
}

Example:

{
  "method": "tools/call",
  "params": {
    "name": "execute_query",
    "arguments": {
      "database_id": "123",
      "query": "SELECT * FROM employees LIMIT 10"
    },
    "_meta": {
      "io.modelcontextprotocol/aiInvocation": {
        "invocationReason": {
          "text": "The user asked to view employee rows, and the employees table was identified as the target table."
        },
        "model": {
          "name": "example-model"
        },
        "userIntent": {
          "text": "Show me 10 employees",
          "redacted": false
        },
        "turnId": "opaque-client-generated-id"
      }
    }
  }
}

Intermediaries that forward MCP requests SHOULD preserve _meta["io.modelcontextprotocol/aiInvocation"] unmodified unless they have a specific policy reason to remove or redact it.

Key naming and namespace

This SEP places all fields under a single _meta key in the MCP-reserved namespace:

io.modelcontextprotocol/aiInvocation

Under the current _meta key-name rules, any prefix whose second label is modelcontextprotocol or mcp is reserved for MCP use (for example io.modelcontextprotocol/, dev.mcp/); see PR #2758, which aligned the reserved-prefix rule in the schema with the specification docs. First-party MCP keys already use this form (e.g. io.modelcontextprotocol/clientInfo, io.modelcontextprotocol/protocolVersion, io.modelcontextprotocol/tasks).

Because this prefix is reserved for MCP, the key proposed here is not something a third party may self-assign. This SEP requests that, if accepted, MCP define io.modelcontextprotocol/aiInvocation as a protocol-level key. Until this SEP is accepted, conformant implementations that want to experiment SHOULD use a vendor-owned reverse-DNS prefix (for example com.example/aiInvocation).

A single namespaced envelope is used deliberately instead of bare keys such as _meta.invocationReason or _meta.turnId. Bare keys are curated by MCP and risk future collision; grouping the fields under one reserved key keeps them together and provides a single, clear extension point.

Historical context: PR #775 expanded _meta name reservation and the issue SEP-1788 ("Clarify _meta Property Reserved Keys") tracks reserved-key behavior. Note that the earlier reservation grammar (pre-#2758) would not have reserved io.modelcontextprotocol/; implementers should follow the current second-label rule.

invocationReason

invocationReason describes why the AI/client requested this specific MCP operation.

Semantics:

This SEP intentionally does not standardize invocation reason categories, retry attempt ordering, policy outcomes, approval lifecycle states, or server-side decision records.

model

model identifies the model that produced the invocation, when known.

Semantics:

This SEP intentionally standardizes only model.name. Provider, gateway, proxy, or version attribution may be added by future SEPs or implementation-specific metadata if interoperability demand is demonstrated.

userIntent

userIntent describes the user-level intent that caused the work, when safe and appropriate to provide.

Semantics:

turnId

turnId correlates MCP requests that were caused by the same user turn.

A user turn means one user input or initiating event that may produce multiple MCP client-to-server requests.

Semantics:

Example with multiple requests in one turn:

{
  "method": "tools/call",
  "params": {
    "name": "list_tables",
    "arguments": {
      "database_id": "123"
    },
    "_meta": {
      "io.modelcontextprotocol/aiInvocation": {
        "invocationReason": {
          "text": "Need to inspect available tables before generating a query."
        },
        "model": {
          "name": "example-model"
        },
        "userIntent": {
          "text": "Show me 10 employees"
        },
        "turnId": "2f4f0c9e-8d72-4c66-9c6f-3f7f3e6a1c9f"
      }
    }
  }
}
{
  "method": "tools/call",
  "params": {
    "name": "execute_query",
    "arguments": {
      "database_id": "123",
      "query": "SELECT * FROM employees LIMIT 10"
    },
    "_meta": {
      "io.modelcontextprotocol/aiInvocation": {
        "invocationReason": {
          "text": "The employees table was identified and the user requested sample employee rows."
        },
        "model": {
          "name": "example-model"
        },
        "userIntent": {
          "text": "Show me 10 employees"
        },
        "turnId": "2f4f0c9e-8d72-4c66-9c6f-3f7f3e6a1c9f"
      }
    }
  }
}

Specification and schema impact

If accepted, adoption requires declaring io.modelcontextprotocol/aiInvocation in the canonical schema (schema.ts, with schema.json regenerated). A decision is needed on whether the envelope is expressed as a typed object on request _meta (a named metadata type) or documented as a free-form reserved key; there is no separate key-registry file in the repository today.

The optional server-side response echo of turnId (see the turnId section) carries the same key in result _meta — _meta["io.modelcontextprotocol/aiInvocation"] with only turnId populated — and should be specified alongside the request-side key so that request and response uses of the key stay consistent.

Rationale

Why _meta?

MCP already uses _meta for protocol metadata that is not part of the application-level tool arguments. Audit context such as model attribution, invocation reason, user intent, and turn correlation is not tool-specific input. Placing it in _meta avoids polluting tool schemas.

Why one namespaced envelope?

A single reserved key avoids claiming multiple bare _meta keys and keeps related AI invocation audit fields together. It also gives future revisions one clear extension point without expanding the top-level _meta surface.

Why not tool arguments?

Tool arguments are part of each tool's semantic contract. Audit fields such as user_query or invocation_reason are useful locally but force every server to define its own convention. They also make audit metadata look like business input.

Why not JSON-RPC id?

JSON-RPC id identifies a single request/response exchange. turnId groups multiple MCP requests caused by the same user turn. The cardinality is different.

Why not progressToken?

progressToken is for progress reporting within a long-running request. turnId is for correlating multiple requests caused by one user turn.

Why not OpenTelemetry only?

OpenTelemetry is useful for backend service tracing, and SEP-414 documents trace context propagation in _meta. However, AI invocation audit has AI-specific concepts such as user intent, AI-generated invocation rationale, and model attribution. turnId is also a user-turn grouping key, not a distributed trace identifier. This SEP does not require or prohibit deployments from additionally carrying trace context through OTel keys or baggage.

All proposals referenced below are open at the time of writing unless noted as merged.

SEP-2787: Tool call attestation (open)

SEP-2787 proposes signed tool-call attestation for MCP, binding invocation context (such as intent and tool/argument information) into a verifiable record. This SEP is narrower and intentionally non-cryptographic: it defines optional client-asserted input-audit metadata and does not define signatures, argument hashes, execution proofs, or server-authoritative decisions. The two are complementary rather than competing — a deployment that needs cryptographic assurance could carry signed attestations (SEP-2787) alongside this lightweight, unsigned input-audit context, where an unsigned invocationReason is the lightweight counterpart of an attested rationale.

SEP-414: OpenTelemetry trace context (merged)

SEP-414 documents using _meta to carry W3C Trace Context (traceparent, tracestate, baggage). It deliberately uses bare (unprefixed) keys as a narrowly-justified exception, because those names mirror an external wire-format standard. This SEP has no such external wire-format to match, so it uses the MCP-reserved namespaced key rather than bare keys; SEP-414 is therefore a useful contrast that explains when bare keys are and are not appropriate. SEP-414 also addresses distributed-trace propagation, which is complementary to, and distinct from, AI-invocation audit semantics.

_meta key reservation: SEP-1788 (open), PR #775 (merged), PR #2758 (merged)

These define and refine how _meta keys are reserved. This SEP follows the current rule established by PR #2758 by requesting a key under the MCP-reserved io.modelcontextprotocol/ namespace.

SEP-2643: Structured Authorization Denials (open)

SEP-2643 defines one server-side decision/denial path — a structured record of an authorization denial. This SEP is the complementary client-asserted input half ("why the AI asked"); it does not define decision or denial records and is designed to compose with server-side records such as SEP-2643.

SEP-2061: Action Security Metadata for MCP Tools (open)

SEP-2061 covers tool side-effect classification and action security metadata, which this SEP lists as a Non-Goal; that work belongs there.

SEP-2448 and SEP-2028: telemetry (open)

SEP-2448 (server execution telemetry) and SEP-2028 (_meta-to-HTTP-header forwarding for distributed tracing) address operational and trace telemetry through _meta. They are complementary to, and distinct from, client-asserted AI-invocation audit semantics.

Backward Compatibility

This proposal is backward compatible.

All fields are optional. Servers that do not understand this _meta entry may ignore it. Clients that do not provide it remain valid.

Existing server-specific conventions, such as tool arguments named user_query or invocation_reason, may continue to work. Implementations may accept both existing custom fields and the standardized _meta form during migration.

Security Implications

This proposal introduces audit metadata, not authorization metadata.

Security considerations:

Servers MUST NOT use invocationReason, userIntent, or model as authorization evidence. Authorization should rely on deterministic server-side context such as authenticated principal, scopes, tool metadata, policy, environment, approval state, and server-side checks.

Audit consumers that summarize or export this metadata should preserve the distinction between client-asserted context and server- or intermediary-witnessed facts. Derived reports or counters must not promote aiInvocation fields into authorization, execution, or outcome evidence.

Servers SHOULD apply local policy for logging, redaction, retention, and access control before persisting these fields.

Servers SHOULD neutralize log-forging vectors (for example CR/LF and other control characters) before persisting these free-text fields, and audit viewers SHOULD apply contextual output-encoding when rendering them to prevent stored-XSS.

Implementations SHOULD enforce per-field and total byte limits for this audit context to avoid excessive log growth or denial-of-service via large metadata.

Non-Goals

This SEP does not define:

Those are valuable follow-up topics, but they should not block standardizing the minimum client-asserted input-audit context.

Reference Implementation

TadpoleDBHub has implemented a server-specific version of this audit context for DB/MCP execution audit.

Current implementation data point:

This validates the operational need, but also shows the drawback of server-specific argument injection. A protocol-level _meta convention would avoid polluting tool schemas and make cross-server audit tooling easier.

Additional implementation feedback from the discussion:

Open Questions

  1. Should this convention apply to all client-to-server requests, or only AI-selected operations such as tools/call?
  2. What length guidance should be recommended for invocationReason.text and userIntent.text?
  3. Should a future revision define a userIntent hash/reference field, and if so with a self-describing format (for example sha256:<hex>)?
  4. Should the optional turnId response echo remain MAY, or be strengthened to SHOULD for asynchronous/streamed transports?

Acknowledgments

This proposal grew out of MCP Discussion #2704. Thanks to the participants there for shaping the key framing — separating client-asserted input audit from server-authoritative decision records, the turnId user-turn correlation model, and the privacy guidance for userIntent.

AI Assistance Disclosure

This SEP draft was prepared with AI assistance for structure, wording, and review. The proposal direction, implementation experience, and final technical judgment were reviewed and edited by the author.