Skip to content

Artifacts

Artifact storage abstractions for persisting and referencing large or binary tool outputs, with in-memory and no-op implementations and auto-discovery.

artifacts

Artifact storage for binary and large text content.

This module provides the ArtifactStore protocol and implementations for storing binary content (PDFs, images, etc.) and large text out-of-band, keeping only compact ArtifactRef references in LLM context.

See RFC: docs/RFC_MCP_BINARY_CONTENT_HANDLING.md

ArtifactStore

Bases: Protocol

Protocol for binary/large-text artifact storage.

Implementations must be async-safe. The protocol is designed to be backend-agnostic - implementations can use memory, disk, S3, etc.

put_bytes async

put_bytes(data: bytes, *, mime_type: str | None = None, filename: str | None = None, namespace: str | None = None, scope: ArtifactScope | None = None, meta: dict[str, Any] | None = None) -> ArtifactRef

Store binary data and return a compact reference.

Parameters:

Name Type Description Default
data bytes

The binary content to store.

required
mime_type str | None

MIME type of the content.

None
filename str | None

Suggested filename for downloads.

None
namespace str | None

Namespace prefix for the artifact ID (e.g., tool name).

None
scope ArtifactScope | None

Scoping information for access control.

None
meta dict[str, Any] | None

Additional metadata to include in the reference.

None

Returns:

Type Description
ArtifactRef

ArtifactRef with the artifact ID and metadata.

put_text async

put_text(text: str, *, mime_type: str = 'text/plain', filename: str | None = None, namespace: str | None = None, scope: ArtifactScope | None = None, meta: dict[str, Any] | None = None) -> ArtifactRef

Store large text and return a compact reference.

Parameters:

Name Type Description Default
text str

The text content to store.

required
mime_type str

MIME type (default: text/plain).

'text/plain'
filename str | None

Suggested filename for downloads.

None
namespace str | None

Namespace prefix for the artifact ID.

None
scope ArtifactScope | None

Scoping information for access control.

None
meta dict[str, Any] | None

Additional metadata to include in the reference.

None

Returns:

Type Description
ArtifactRef

ArtifactRef with the artifact ID and metadata.

get async

get(artifact_id: str) -> bytes | None

Retrieve artifact bytes by ID.

Parameters:

Name Type Description Default
artifact_id str

The artifact ID from an ArtifactRef.

required

Returns:

Type Description
bytes | None

The binary content, or None if not found or expired.

get_ref async

get_ref(artifact_id: str) -> ArtifactRef | None

Retrieve artifact metadata by ID.

Parameters:

Name Type Description Default
artifact_id str

The artifact ID.

required

Returns:

Type Description
ArtifactRef | None

The ArtifactRef with metadata, or None if not found.

delete async

delete(artifact_id: str) -> bool

Delete an artifact.

Parameters:

Name Type Description Default
artifact_id str

The artifact ID to delete.

required

Returns:

Type Description
bool

True if deleted, False if not found.

exists async

exists(artifact_id: str) -> bool

Check if an artifact exists.

Parameters:

Name Type Description Default
artifact_id str

The artifact ID to check.

required

Returns:

Type Description
bool

True if the artifact exists and hasn't expired.

list async

list(*, scope: ArtifactScope | None = None) -> list[ArtifactRef]

List artifacts matching the given scope filter.

None fields in scope = don't filter on that dimension. If scope is None, returns all artifacts.

ArtifactRef

Bases: BaseModel

Compact reference to a stored artifact.

This is the only artifact-related data that should appear in LLM context. Never include raw bytes or base64 in observations - only ArtifactRef.

id instance-attribute

id: str

Unique artifact identifier (typically namespace + content hash).

mime_type class-attribute instance-attribute

mime_type: str | None = None

MIME type of the content (e.g., 'application/pdf', 'image/png').

size_bytes class-attribute instance-attribute

size_bytes: int | None = None

Size of the artifact in bytes.

filename class-attribute instance-attribute

filename: str | None = None

Original or suggested filename for downloads.

sha256 class-attribute instance-attribute

sha256: str | None = None

SHA-256 hash of the content for integrity verification.

scope class-attribute instance-attribute

scope: ArtifactScope | None = None

Scoping information for access control.

namespace class-attribute instance-attribute

namespace: str | None = None

Namespace for artifact grouping (e.g., tool name).

source class-attribute instance-attribute

source: dict[str, Any] = Field(default_factory=dict)

Additional metadata (tool name, warnings, preview, etc.).

ArtifactScope

Bases: BaseModel

Scoping information for access control.

The host (HTTP layer) enforces access control based on these fields. ArtifactStore implementations store this metadata but don't enforce access.

tenant_id class-attribute instance-attribute

tenant_id: str | None = None

user_id class-attribute instance-attribute

user_id: str | None = None

session_id class-attribute instance-attribute

session_id: str | None = None

trace_id class-attribute instance-attribute

trace_id: str | None = None

ArtifactRetentionConfig

Bases: BaseModel

Retention policy for artifacts.

ttl_seconds class-attribute instance-attribute

ttl_seconds: int = 3600

Artifacts expire after this many seconds. Default: 1 hour.

max_artifact_bytes class-attribute instance-attribute

max_artifact_bytes: int = 50 * 1024 * 1024

Maximum size per artifact. Default: 50MB.

max_session_bytes class-attribute instance-attribute

max_session_bytes: int = 500 * 1024 * 1024

Maximum total bytes per session. Default: 500MB.

max_trace_bytes class-attribute instance-attribute

max_trace_bytes: int = 100 * 1024 * 1024

Maximum total bytes per trace. Default: 100MB.

max_artifacts_per_trace class-attribute instance-attribute

max_artifacts_per_trace: int = 100

Maximum artifacts per trace.

max_artifacts_per_session class-attribute instance-attribute

max_artifacts_per_session: int = 1000

Maximum artifacts per session.

cleanup_strategy class-attribute instance-attribute

cleanup_strategy: Literal['lru', 'fifo', 'none'] = 'lru'

Strategy for evicting artifacts when limits are reached.

InMemoryArtifactStore

InMemoryArtifactStore(retention: ArtifactRetentionConfig | None = None, scope_filter: ArtifactScope | None = None)

In-memory artifact store for development and testing.

Implements LRU eviction and TTL expiration. Suitable for Playground and test environments. Not suitable for production (no persistence).

Initialize the in-memory store.

Parameters:

Name Type Description Default
retention ArtifactRetentionConfig | None

Retention policy configuration.

None
scope_filter ArtifactScope | None

If provided, all artifacts are scoped to this.

None

total_bytes property

total_bytes: int

Total bytes currently stored.

count property

count: int

Number of artifacts currently stored.

put_bytes async

put_bytes(data: bytes, *, mime_type: str | None = None, filename: str | None = None, namespace: str | None = None, scope: ArtifactScope | None = None, meta: dict[str, Any] | None = None) -> ArtifactRef

Store binary data in memory.

put_text async

put_text(text: str, *, mime_type: str = 'text/plain', filename: str | None = None, namespace: str | None = None, scope: ArtifactScope | None = None, meta: dict[str, Any] | None = None) -> ArtifactRef

Store text as UTF-8 bytes.

get async

get(artifact_id: str) -> bytes | None

Retrieve artifact bytes by ID.

get_ref async

get_ref(artifact_id: str) -> ArtifactRef | None

Retrieve artifact metadata by ID.

delete async

delete(artifact_id: str) -> bool

Delete an artifact.

exists async

exists(artifact_id: str) -> bool

Check if an artifact exists.

list async

list(*, scope: ArtifactScope | None = None) -> list[ArtifactRef]

List artifacts matching the given scope filter.

clear

clear() -> None

Clear all artifacts (for testing/cleanup).

NoOpArtifactStore

NoOpArtifactStore(max_inline_preview: int = 500)

Fallback store that logs warnings but doesn't persist content.

Used when no real ArtifactStore is configured. Returns truncated references with warnings so the system continues to function.

put_bytes async

put_bytes(data: bytes, *, mime_type: str | None = None, filename: str | None = None, namespace: str | None = None, scope: ArtifactScope | None = None, meta: dict[str, Any] | None = None) -> ArtifactRef

Store binary data (no-op: logs warning, returns truncated ref).

put_text async

put_text(text: str, *, mime_type: str = 'text/plain', filename: str | None = None, namespace: str | None = None, scope: ArtifactScope | None = None, meta: dict[str, Any] | None = None) -> ArtifactRef

Store large text (no-op: logs warning, returns truncated ref with preview).

get async

get(artifact_id: str) -> bytes | None

Cannot retrieve from no-op store.

get_ref async

get_ref(artifact_id: str) -> ArtifactRef | None

Cannot retrieve metadata from no-op store.

delete async

delete(artifact_id: str) -> bool

No-op delete always returns False.

exists async

exists(artifact_id: str) -> bool

No-op store never has artifacts.

list async

list(*, scope: ArtifactScope | None = None) -> list[ArtifactRef]

No-op list always returns empty.

discover_artifact_store

discover_artifact_store(state_store: Any) -> ArtifactStore | None

Attempt to discover ArtifactStore from state_store via duck-typing.

Checks for: 1. state_store.artifact_store attribute (preferred) 2. state_store implementing ArtifactStore protocol directly

Parameters:

Name Type Description Default
state_store Any

A StateStore or similar object that might provide artifacts.

required

Returns:

Type Description
ArtifactStore | None

An ArtifactStore if discovered, None otherwise.