Quickstart
Run a Claude Agent SDK Python agent behind CopilotKit.
This quickstart gives you two working paths:
- Start from scratch to scaffold the full Claude Agent SDK Python showcase.
- Use an existing agent to expose your own Claude Agent SDK process over AG-UI and connect it to a React app.
Prerequisites#
Before you begin, you'll need the following:
- An Anthropic API key
- Python 3.11+
- Node.js 20+
uvfor Python dependency management- Your favorite JavaScript package manager
Getting started#
Choose your starting point#
Verify the integration#
Open http://localhost:3000 and ask:
Tell me in one sentence what this app can do.If the chat streams a Claude response, CopilotKit is connected to your Claude Agent SDK Python agent.
Troubleshooting
- If the runtime cannot reach the agent, check that the FastAPI server is running on
http://localhost:8000or setAGENT_URLin the frontend. - If Claude returns an authentication error, make sure
ANTHROPIC_API_KEYis available to the Python process. - If a custom tool call stalls, add the backend tool bridge shown below instead of leaving
"tools": [].
Backend tools and state#
The minimal server above is enough for text chat. To support backend tools, shared state snapshots, and richer demos, expose your tool schemas through the Claude SDK MCP bridge and emit AG-UI state events:
Install the Claude Agent SDK packages
uv add claude-agent-sdk ag-ui-claude-sdk ag-ui-protocol anthropic fastapi uvicorn python-dotenvBridge Claude Agent SDK to AG-UI
Use ClaudeAgentAdapter from ag_ui_claude_sdk. The adapter receives the
AG-UI run input, emits AG-UI events back to CopilotKit, and can expose
backend tools through an in-process Claude SDK MCP server.
async def run_with_claude_agent_sdk(
input_data: RunAgentInput,
*,
system_prompt: str,
tools: list[dict[str, Any]],
state: Any,
model: str,
execute_tool: ExecuteTool,
max_turns: int = 10,
) -> AsyncIterator[str]:
"""Run through the official AG-UI Claude adapter and emit SSE chunks."""
encoder = EventEncoder()
state_box = {"state": state}
pending_state_snapshots: list[Any] = []
sdk_tools = _build_sdk_tools(
tools,
execute_tool=execute_tool,
get_state=lambda: state_box["state"],
set_state=lambda next_state: _set_state(
next_state,
state_box,
pending_state_snapshots,
),
)
options: dict[str, Any] = {
"model": _normalize_claude_agent_sdk_model(model),
"system_prompt": system_prompt,
"tools": [],
"permission_mode": "dontAsk",
"max_turns": max_turns,
}
if sdk_tools:
options["mcp_servers"] = {
COPILOTKIT_MCP_SERVER_NAME: create_sdk_mcp_server(
COPILOTKIT_MCP_SERVER_NAME,
"1.0.0",
tools=sdk_tools,
)
}
options["allowed_tools"] = [
f"{COPILOTKIT_TOOL_PREFIX}{schema['name']}" for schema in tools
]
adapter = ClaudeAgentAdapter(
name="claude-sdk-python",
options=options,
)
run_input = _with_initial_state(input_data, state)
async for event in adapter.run(run_input):
if event.type == EventType.TOOL_CALL_RESULT and pending_state_snapshots:
yield encoder.encode(
StateSnapshotEvent(
type=EventType.STATE_SNAPSHOT,
snapshot=pending_state_snapshots.pop(0),
)
)
yield encoder.encode(event)