Skip to main content
Agent SDK Python MCP Session Management API

Claude Agent SDK: Session History, MCP Control, and Thinking Config

Claude Agent SDK Python v0.1.36-0.1.48 adds session history queries, runtime MCP server management, typed task messages, thinking configuration, and effort control.

March 6, 2026 7 min read By Claude World

The Claude Agent SDK for Python shipped a significant batch of features across versions 0.1.36 through 0.1.48. This article covers the highlights: session history queries, runtime MCP server management, typed task messages, thinking configuration, and several important bug fixes.

All code examples assume claude_agent_sdk is installed at v0.1.48 or later.


Session History (v0.1.46)

Two new top-level functions let you read past session data without touching the raw JSONL transcript files. Both are synchronous, filesystem-only operations — no running Claude process required.

list_sessions()

Returns a list of SDKSessionInfo objects sorted by last-modified time. Each entry contains the session ID, a display summary, file size, optional custom title, first prompt, git branch, and working directory.

from claude_agent_sdk import list_sessions

# All sessions for a specific project
sessions = list_sessions(directory="/path/to/project")

for s in sessions:
    print(f"{s.session_id}  {s.summary}  ({s.file_size} bytes)")

# All sessions across every project
all_sessions = list_sessions()

# Limit results and skip git worktree scanning
recent = list_sessions(
    directory="/path/to/project",
    limit=10,
    include_worktrees=False,
)

get_session_messages()

Returns the full message list for a session as SessionMessage objects. Each message carries its type (“user” or “assistant”), uuid, session_id, and the raw Anthropic API message dict.

from claude_agent_sdk import get_session_messages

messages = get_session_messages(
    "550e8400-e29b-41d4-a716-446655440000",
    directory="/path/to/project",
)

for msg in messages:
    role = msg.type
    content = msg.message.get("content", "")
    print(f"[{role}] {content[:80]}...")

# Paginate through a long session
page = get_session_messages(
    "550e8400-e29b-41d4-a716-446655440000",
    limit=20,
    offset=40,
)

Use cases: building admin dashboards, audit logs, session replay UIs, or analytics pipelines that aggregate token usage across sessions.


MCP Runtime Control (v0.1.46)

The SDK now supports adding and removing MCP servers while a session is already running. Two new methods on ClaudeSDKClientadd_mcp_server() and remove_mcp_server() — let you change the available tool set without restarting the conversation.

The existing toggle_mcp_server() and reconnect_mcp_server() methods complement this by enabling/disabling or reconnecting servers that are already configured.

from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

options = ClaudeAgentOptions(
    model="claude-sonnet-4-5",
    permission_mode="bypassPermissions",
)

async with ClaudeSDKClient(options) as client:
    await client.query("Analyze project structure")

    # Check which MCP servers are connected
    status = await client.get_mcp_status()
    for server in status["mcpServers"]:
        print(f"{server['name']}: {server['status']}")

    # Temporarily disable a server
    await client.toggle_mcp_server("heavy-analytics", enabled=False)
    await client.query("Quick task without analytics tools")

    # Re-enable it
    await client.toggle_mcp_server("heavy-analytics", enabled=True)

    # Reconnect a failed server
    for server in status["mcpServers"]:
        if server["status"] == "failed":
            await client.reconnect_mcp_server(server["name"])

The McpServerStatus TypedDict provides typed access to status fields: name, status (one of "connected", "pending", "failed", "needs-auth", "disabled"), optional error, config, scope, serverInfo, and tools.

Use case: adaptive tool availability. A coding agent can load a database MCP server only when the task involves database work, then remove it when switching to frontend tasks — keeping the tool list focused and reducing prompt clutter.


Typed Task Messages (v0.1.46)

Previously, all task lifecycle events arrived as generic SystemMessage objects, forcing you to inspect the subtype and data fields manually. Three new message subclasses provide structured access:

ClassSubtypeKey Fields
TaskStartedMessagetask_startedtask_id, description, session_id, task_type
TaskProgressMessagetask_progresstask_id, description, usage, last_tool_name
TaskNotificationMessagetask_notificationtask_id, status, summary, output_file

All three inherit from SystemMessage, so existing isinstance(msg, SystemMessage) checks still match.

from claude_agent_sdk import (
    ClaudeSDKClient,
    TaskStartedMessage,
    TaskProgressMessage,
    TaskNotificationMessage,
)

async with ClaudeSDKClient(options) as client:
    await client.query("Run the full test suite")

    async for msg in client.receive_messages():
        match msg:
            case TaskStartedMessage():
                print(f"Task {msg.task_id} started: {msg.description}")

            case TaskProgressMessage():
                tokens = msg.usage.input_tokens + msg.usage.output_tokens
                print(f"  Progress: {tokens} tokens, last tool: {msg.last_tool_name}")

            case TaskNotificationMessage():
                print(f"Task {msg.task_id} {msg.status}: {msg.summary}")
                if msg.status == "failed":
                    print(f"  Output: {msg.output_file}")

TaskNotificationStatus is a Literal["completed", "failed", "stopped"] — use it for exhaustive match checks.

Use case: building progress UIs that show real-time task status with token counts, or orchestrators that react to task completion/failure events.


ResultMessage stop_reason (v0.1.46)

ResultMessage gained a stop_reason field (optional string) that tells you why a conversation turn ended. Combined with the existing is_error, num_turns, and total_cost_usd fields, you get a complete picture of how and why a query terminated.

from claude_agent_sdk import ResultMessage

async for msg in client.receive_response():
    if isinstance(msg, ResultMessage):
        print(f"Stopped: {msg.stop_reason}")
        print(f"Turns: {msg.num_turns}, Cost: ${msg.total_cost_usd:.4f}")
        print(f"Error: {msg.is_error}")

Thinking Configuration (v0.1.36)

Extended thinking now has a proper configuration system. The thinking field on ClaudeAgentOptions replaces the deprecated max_thinking_tokens and accepts three config shapes:

from claude_agent_sdk import (
    ClaudeAgentOptions,
    ThinkingConfigAdaptive,
    ThinkingConfigEnabled,
    ThinkingConfigDisabled,
    query,
)

# Adaptive: let the model decide when and how much to think
options_adaptive = ClaudeAgentOptions(
    model="claude-opus-4-6",
    thinking=ThinkingConfigAdaptive(type="adaptive"),
)

# Enabled with a specific token budget
options_budgeted = ClaudeAgentOptions(
    model="claude-opus-4-6",
    thinking=ThinkingConfigEnabled(type="enabled", budget_tokens=10000),
)

# Disabled entirely
options_fast = ClaudeAgentOptions(
    model="claude-sonnet-4-5",
    thinking=ThinkingConfigDisabled(type="disabled"),
)

Effort Control

The effort field provides a simpler way to control thinking depth without specifying exact token budgets:

# Quick, low-effort response
options = ClaudeAgentOptions(
    model="claude-sonnet-4-5",
    effort="low",
)

# Maximum reasoning depth
options = ClaudeAgentOptions(
    model="claude-opus-4-6",
    effort="max",
)

Valid values: "low", "medium", "high", "max". The effort option is independent of thinking — you can use either or both.


Hook Enhancements (v0.1.46)

Tool-lifecycle hook inputs now include agent_id and agent_type fields. These are critical for multi-agent setups where multiple sub-agents run in parallel and their tool-lifecycle hooks interleave over the same control channel.

The fields appear on PreToolUseHookInput, PostToolUseHookInput, and PostToolUseFailureHookInput:

  • agent_id — present when the hook fires from inside a Task-spawned sub-agent. This is the only reliable way to attribute a tool call to a specific sub-agent when multiple agents run concurrently.
  • agent_type — the agent type name (e.g., “general-purpose”, “code-reviewer”). Present inside a sub-agent alongside agent_id, or on the main thread of a session started with --agent.
options = ClaudeAgentOptions(
    hooks={
        "PreToolUse": [
            HookMatcher(
                matcher="Edit",
                hooks=[HookCallback(callback=my_hook)],
            )
        ]
    }
)

async def my_hook(input_data):
    agent = input_data.get("agent_id", "main")
    agent_type = input_data.get("agent_type", "unknown")
    tool = input_data["tool_name"]
    print(f"[{agent_type}:{agent}] using {tool}")
    return {"decision": "approve"}

Bug Fixes

Three notable fixes landed across these versions:

Fine-grained tool streaming (v0.1.48)include_partial_messages=True was not delivering input_json_delta events because the CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING environment variable was not being set in the subprocess. This regression affected versions 0.1.36 through 0.1.47 for users without the server-side feature flag.

String prompt MCP initialization (v0.1.46) — Passing a plain string prompt would close stdin before MCP server initialization completed, causing MCP servers to fail to register. This is now fixed.

Unknown message type handling (v0.1.40) — Unrecognized CLI message types (e.g., rate_limit_event) would crash the session by raising MessageParseError. Unknown types are now silently skipped, making the SDK forward-compatible with future CLI message additions.


Upgrade Path

The changes are additive. No breaking changes were introduced between v0.1.36 and v0.1.48. The only deprecation is max_thinking_tokens in favor of the thinking field — the old field still works but thinking takes precedence when both are set.

pip install --upgrade claude-agent-sdk

The bundled Claude CLI was updated through these versions from v2.1.42 to v2.1.71. If you pin a specific CLI version via cli_path, make sure it is at least v2.1.69 to get the full feature set described here.