SDK Claude Code MCP Accounting

KDCube Claude Code Integration

A bundle-scoped Claude Code subprocess runner with SDK-managed workspace files, MCP server config, KDCube skill materialization, communicator streaming, and standard KDCube LLM accounting.

workspace-scoped, not sandboxed .mcp.json generated by SDK KDCube skills → Claude project skills runtime=claude_code accounting

What This Integration Is

Claude Code support in KDCube is a subagent runtime for bundles. React remains the primary chat/task decision loop. A bundle can delegate scoped work to Claude Code when the job benefits from Claude's own file-oriented loop, built-in tools, and MCP client support.

The SDK owns the reusable runner and workspace file generation. The bundle still owns product policy: which MCP servers to expose, which tools are allowed, which instructions are safe, which secrets are passed, and which workspace is appropriate for the current user/task boundary.

i

Important distinction: Claude Code does not automatically inherit React tools or React skills. If both runtimes need the same capability, expose it separately for each runtime.

RuntimeTool sourceSkill sourceTypical use
React tools_descriptor.py, MCP_TOOL_SPECS, SDK tools skills_descriptor.py and bundle skills/ Main chat turns, task executions, transport-backed assistant work
Claude Code Claude built-ins plus generated Claude MCP config CLAUDE.md and native Claude project Skills under .claude/skills Scoped code/file/research subagent work, specialized MCP loops, private sub-processing

Runtime Model

The runner starts a fresh claude -p subprocess for each turn and uses a deterministic Claude session id for continuity. That session id is derived from the current KDCube user, conversation, and Claude agent name, so two users do not collide while the same user can keep separate Claude sessions by using separate conversations or agent names.

Fresh subprocess per turn

The SDK runs Claude Code in print mode with stream-json. Follow-up and steer turns resume the same Claude session identity.

Explicit config

The caller provides workspace_path, model, allowed tools, permission mode, timeout, env, and optional workspace config.

Communicator streaming

The runner emits chat.step and chat.delta events through the normal KDCube communicator path.

Core API surface

InputSDK surfaceMeaning
Agent identityagent_namePart of session identity and accounting metadata
Workspaceworkspace_pathClaude subprocess working directory
Additional pathsadditional_directoriesPassed to Claude as --add-dir
Built-in toolsallowed_toolsForwarded as Claude --allowedTools
Workspace filesClaudeCodeWorkspaceConfigLets the SDK write .mcp.json, settings, CLAUDE.md, and project Skills
Structured progressstructured_output_prefixesParses caller-defined line-framed JSON records from streamed text
Timeouttimeout_secondsMarks the run failed and terminates when exceeded

Workspace And Session Continuity

The bundle chooses the local workspace root. The SDK can optionally hydrate and publish Claude's own session files through a local or git-backed session store, but KDCube conversation JSON is not Claude's continuity substrate.

  1. 1

    Choose a deterministic workspace

    Use a per-user, per-task, or per-conversation path that matches the privacy boundary of the work.

  2. 2

    Prepare support files

    The SDK writes standard Claude workspace files from ClaudeCodeWorkspaceConfig.

  3. 3

    Run Claude

    The subprocess runs from workspace_path and receives configured --add-dir paths.

  4. 4

    Persist continuity if enabled

    The git-backed store uses one branch per tenant, project, user, conversation, and agent.

!

Workspace scoping is not security isolation. workspace_path means "run Claude from this directory." additional_directories means "also pass these paths through --add-dir." It is not a chroot, container, filesystem jail, or privilege boundary.

MCP Integration

There are three different MCP surfaces. Keeping them separate prevents most integration mistakes.

ConceptConfigured byConsumer
React MCP client configBundle props config.mcp.services plus MCP_TOOL_SPECSKDCube tool subsystem
Bundle-served MCP endpoint@mcp(...) on the bundle entrypointExternal MCP clients, Claude Code, other services
Claude Code MCP configGenerated .mcp.json in the Claude workspaceThe claude CLI subprocess

An @mcp(...) endpoint exposes a bundle MCP server. It does not automatically tell Claude how to connect to it. The bundle should pass a ClaudeCodeWorkspaceConfig with mcp_servers, enabled servers, and allowed tool names so the SDK can write Claude-compatible workspace files.

i

Reachability rule: the MCP URL must be reachable from the process or container that runs claude. 127.0.0.1 is correct only when the MCP server and Claude client share the same network namespace.

Deployment reachability examples

  • Same local process/container: http://127.0.0.1:<port> can work.
  • Different Docker containers: use Docker service DNS or another internal host name.
  • ECS same task with shared task networking: localhost can work if the target container listens there.
  • ECS separate task/service: use Cloud Map, an internal ALB, or another private endpoint.

Skills: React vs Claude Code

React skills and Claude Code Skills are not the same object at runtime. KDCube now bridges them by letting bundles pass KDCube skill ids to ClaudeCodeWorkspaceConfig.skill_ids. The SDK resolves those ids through the active skills subsystem, expands imports, copies support files, and writes native Claude Code project Skills under .claude/skills.

CLAUDE.md

Broad workspace instruction. Use it for global constraints, task policy, and scenario rules.

Project Skill

Discoverable capability folder. Claude loads the full SKILL.md only when the task matches the skill description.

workspace/
  .mcp.json
  CLAUDE.md
  .claude/
    settings.local.json
    skills/
      email-analysis/
        SKILL.md
        reference.md
        scripts/
          helper.py

Tool access is still separate. A Claude Skill can guide behavior, but Claude can only call tools that the workspace MCP config and Claude tool allow-list make available.

Streaming And Accounting

Claude Code runs emit normal KDCube LLM accounting events. The accounting metadata marks the runtime as claude_code, while usage comes from the Claude CLI stream.

Usage

Input, output, thinking, cache read, cache creation, request count, and provider-reported cost when available.

Result fields

Bundles can persist model, usage, cost_usd, structured_events, and final_text.

Progress

chat.step shows start/stderr/completion, while chat.delta streams incremental text.

service_type = "llm"
provider = "anthropic"
metadata.runtime = "claude_code"
metadata.agent_name = "email-processor"
metadata.claude_session_id = "<stable uuid>"

Safety Boundary And Runtime Requirements

!

Claude Code is not sandboxed by this SDK. It is a subprocess in the same OS/container security boundary as the runner. If you allow Bash, broad Read, broad Grep, or sensitive env variables, assume Claude can use them.

Minimum deployment requirements

  • The claude CLI exists in the runtime image/container.
  • Anthropic/Claude credentials are available to the subprocess only when intentionally provided.
  • The Claude workspace is writable.
  • The bundle MCP endpoint is enabled when Claude needs bundle tools.
  • Claude can reach the MCP URL over HTTP from its own network namespace.
  • The MCP endpoint authenticates itself; proc does not authenticate bundle MCP on its behalf.
  • FastMCP endpoints are stateless or their lifespan/session manager is correctly initialized.

Policy owned by the bundle

  • Resolve and pass only deliberate short-lived env values or tokens.
  • Allow only the Claude built-ins and MCP tools required for the run.
  • Use run-scoped MCP tokens with user id, run id, scope, and expiry.
  • Do not write durable secrets into .mcp.json, logs, tool outputs, or bundle props.
  • Use a deterministic workspace path that matches the user/task privacy boundary.

Bundle Integration Pattern

This is the current shape: build a workspace config, create a Claude Code agent from the active KDCube request context, and run it through the SDK turn wrapper.

from kdcube_ai_app.apps.chat.sdk.solutions.claude_code import (
    ClaudeCodeAgent,
    ClaudeCodeSessionStoreConfig,
    ClaudeCodeWorkspaceConfig,
    run_claude_code_turn,
)

workspace_config = ClaudeCodeWorkspaceConfig(
    mcp_servers={
        "scoped_data": {
            "type": "http",
            "url": mcp_url,
            "headers": {"X-Run-Token": short_lived_token},
        }
    },
    skill_ids=("product.email-analysis",),
    skill_allowed_tools={
        "product.email-analysis": (
            "mcp__scoped_data__task_context",
            "mcp__scoped_data__list_messages",
            "mcp__scoped_data__get_message",
            "mcp__scoped_data__record_result",
        )
    },
    allowed_tools=(
        "mcp__scoped_data__task_context",
        "mcp__scoped_data__list_messages",
        "mcp__scoped_data__get_message",
        "mcp__scoped_data__record_result",
    ),
    instructions_markdown="# Scoped processor\nUse only the configured MCP tools.\n",
)

agent = ClaudeCodeAgent.from_current_context(
    agent_name="email-processor",
    workspace_path=workspace_root,
    model="sonnet",
    allowed_tools=workspace_config.allowed_tools,
    permission_mode="acceptEdits",
    timeout_seconds=90,
    workspace_config=workspace_config,
)

result = await run_claude_code_turn(
    agent=agent,
    prompt=task_instruction,
    kind="regular",
    session_store=ClaudeCodeSessionStoreConfig(
        implementation="git",
        git_repo=claude_session_repo,
        local_root=workspace_root / ".claude-session",
        tenant=tenant,
        project=project,
        user_id=user_id,
        conversation_id=conversation_id,
        agent_name="email-processor",
    ),
)

Primary documentation