claude-agent-sdk-laravel

mcp
Security Audit
Pass
Health Pass
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Community trust — 14 GitHub stars
Code Pass
  • Code scan — Scanned 7 files during light audit, no dangerous patterns found
Permissions Pass
  • Permissions — No dangerous permissions requested
Purpose
This package is a Laravel/PHP SDK that wraps the Claude Code CLI, enabling developers to build AI agents with tool use, sandboxing, and structured output directly inside their applications.

Security Assessment
Overall risk: Medium. The SDK explicitly enables AI-driven file operations, shell (Bash) command execution, and code editing via the CLI. While the light audit found no hardcoded secrets, backdoors, or dangerous patterns in the scanned files, the tool's core design relies on passing commands to the underlying system. Developers must strictly configure permissions and budget limits (e.g., `permission('acceptEdits')`, `maxBudgetUsd`) to prevent unintended or malicious actions by the AI agent. An Anthropic API key is required, meaning sensitive credentials must be securely stored in the application's environment variables.

Quality Assessment
Quality is promising but early-stage. The project is licensed under the permissive MIT license and was actively updated very recently. It features an excellent setup for a PHP package, including automated testing via GitHub Actions and compatibility with recent Laravel versions (10, 11, and 12). However, community trust is currently minimal, with only 14 GitHub stars. As a relatively new and small project, it may lack the battle-tested reliability of larger, more established packages.

Verdict
Use with caution — the package itself is safe, but granting an AI agent access to Bash and file editing requires strict permission controls to secure your environment.
SUMMARY

Anthropic Claude Agent SDK for PHP & Laravel — build AI agents with tool use, sandboxing, MCP servers, subagents, hooks, and structured output via the Claude Code CLI

README.md

Claude Agent SDK for Laravel

Tests
Latest Version
Total Downloads
PHP Version
Laravel
License

Build AI agents with Claude Code as a library in your Laravel applications. This SDK wraps the Claude Code CLI to give your app access to file operations, bash commands, code editing, web search, subagents, and more.

Requirements

  • PHP 8.1+
  • Laravel 10, 11, or 12
  • Claude Code CLI (npm install -g @anthropic-ai/claude-code)
  • Anthropic API key

Installation

composer require mohamed-ashraf-elsaed/claude-agent-sdk-laravel

Publish the config:

php artisan vendor:publish --tag=claude-agent-config

Add your API key to .env:

ANTHROPIC_API_KEY=your-api-key

Quick Start

Simple Query

use ClaudeAgentSDK\Facades\ClaudeAgent;

$result = ClaudeAgent::query('What files are in this directory?');

echo $result->text();       // Final text result
echo $result->costUsd();    // Cost in USD
echo $result->sessionId;    // Session ID for resuming

With Options (Fluent API)

use ClaudeAgentSDK\Options\ClaudeAgentOptions;

$options = ClaudeAgentOptions::make()
    ->tools(['Read', 'Edit', 'Bash', 'Grep', 'Glob'])
    ->permission('acceptEdits')
    ->maxTurns(10)
    ->maxBudgetUsd(5.00)
    ->cwd('/path/to/project');

$result = ClaudeAgent::query('Find and fix the bug in auth.php', $options);

if ($result->isSuccess()) {
    echo $result->text();
}

Streaming Responses

use ClaudeAgentSDK\Messages\AssistantMessage;
use ClaudeAgentSDK\Messages\ResultMessage;

foreach (ClaudeAgent::stream('Refactor the User model') as $message) {
    if ($message instanceof AssistantMessage) {
        echo $message->text();
    }

    if ($message instanceof ResultMessage) {
        echo "\nDone! Cost: $" . $message->totalCostUsd;
    }
}

Stream with Callback

$result = ClaudeAgent::streamCollect(
    prompt: 'Create a REST API for products',
    onMessage: function ($message) {
        if ($message instanceof AssistantMessage) {
            Log::info($message->text());
        }
    },
    options: ClaudeAgentOptions::make()->tools(['Read', 'Write', 'Bash']),
);

echo $result->text();

Options Reference

Fluent Builder

$options = ClaudeAgentOptions::make()
    ->tools(['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob'])
    ->disallow(['WebFetch'])
    ->model('claude-sonnet-4-5-20250929')
    ->permission('acceptEdits')
    ->maxTurns(15)
    ->maxBudgetUsd(10.00)
    ->maxThinkingTokens(8000)
    ->fallbackModel('claude-haiku-4-5')
    ->cwd('/path/to/project')
    ->env('MY_VAR', 'value')
    ->settingSources(['project'])
    ->useClaudeCodePrompt('Also follow PSR-12.')
    ->betas(['context-1m-2025-08-07'])
    ->permissionPromptToolName('my_custom_tool')
    ->resumeSessionAt('2025-01-15T10:30:00Z')
    ->allowDangerouslySkipPermissions();

From Array

$options = ClaudeAgentOptions::fromArray([
    'allowed_tools' => ['Read', 'Bash'],
    'permission_mode' => 'bypassPermissions',
    'max_turns' => 5,
    'max_budget_usd' => 5.00,
    'max_thinking_tokens' => 10000,
]);

System Prompts

// Custom string
$options->systemPrompt('You are a Laravel expert. Always use Eloquent.');

// Claude Code preset (includes default agent behavior)
$options->useClaudeCodePrompt();

// Claude Code preset with additions
$options->useClaudeCodePrompt('Follow PSR-12 and use strict types.');

Permission Modes

Mode Behavior
default Ask for permission on each tool use
acceptEdits Auto-accept file edits, ask for others
dontAsk Don't ask but log decisions
bypassPermissions Skip all permission checks
plan Create a plan but don't execute

Custom Permission Handler

For fine-grained control over tool permissions, use canUseTool() to register a callback that approves, denies, or modifies each tool invocation:

use ClaudeAgentSDK\Permissions\PermissionResultAllow;
use ClaudeAgentSDK\Permissions\PermissionResultDeny;

$options = ClaudeAgentOptions::make()
    ->canUseTool(function (string $toolName, array $input) {
        if ($toolName === 'Bash' && str_contains($input['command'] ?? '', 'rm -rf')) {
            return new PermissionResultDeny('Destructive commands not allowed');
        }
        if ($toolName === 'Write') {
            return new PermissionResultAllow(
                updatedInput: array_merge($input, ['file_path' => '/sandbox' . $input['file_path']])
            );
        }
        return new PermissionResultAllow();
    });

Return PermissionResultAllow to approve (optionally with modified input), or PermissionResultDeny with a reason to block the tool call.

Hooks

Run shell commands before or after Claude uses tools. The SDK supports all 12 hook events:

PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, Notification, SessionStart, SessionEnd, Stop, SubagentStart, SubagentStop, PreCompact, PermissionRequest

use ClaudeAgentSDK\Hooks\HookEvent;
use ClaudeAgentSDK\Hooks\HookMatcher;

$options = ClaudeAgentOptions::make()
    ->tools(['Read', 'Edit', 'Bash'])
    // Shorthand methods
    ->preToolUse('php artisan lint:check', '/Edit|Write/', 30)
    ->postToolUse('php artisan test:affected')
    // Or use hook() directly for any event
    ->hook(HookEvent::Stop, HookMatcher::command('php /hooks/cleanup.php'))
    ->hook(HookEvent::SessionStart, HookMatcher::command('php /hooks/init.php'))
    ->hook(HookEvent::SubagentStop, HookMatcher::command('php /hooks/subagent-done.php'));

$result = ClaudeAgent::query('Refactor the User model', $options);

HookMatcher Factory Methods

// From a shell command
$matcher = HookMatcher::command('eslint --fix', '/Edit|Write/', 60);

// From a PHP script
$matcher = HookMatcher::phpScript('/hooks/validate.php', '/Bash/', 10);

// Full control
$matcher = new HookMatcher(
    matcher: '/Edit|Write/',
    hooks: ['php /hooks/lint.php', 'php /hooks/backup.php'],
    timeout: 30,
);

Subagents

Define specialized agents that Claude delegates tasks to:

use ClaudeAgentSDK\Agents\AgentDefinition;

$options = ClaudeAgentOptions::make()
    ->tools(['Read', 'Grep', 'Glob', 'Task'])
    ->agent('security-reviewer', new AgentDefinition(
        description: 'Security code review specialist',
        prompt: 'You are a security expert. Find vulnerabilities in PHP/Laravel code.',
        tools: ['Read', 'Grep', 'Glob'],
        model: 'sonnet',
    ))
    ->agent('test-writer', new AgentDefinition(
        description: 'PHPUnit test writer',
        prompt: 'Write comprehensive PHPUnit tests for Laravel applications.',
        tools: ['Read', 'Write', 'Bash'],
    ));

$result = ClaudeAgent::query('Review the auth module for security issues', $options);

Structured Output

Get validated JSON responses matching a schema:

$options = ClaudeAgentOptions::make()
    ->tools(['Read', 'Grep', 'Glob'])
    ->outputFormat([
        'type' => 'object',
        'properties' => [
            'issues' => [
                'type' => 'array',
                'items' => [
                    'type' => 'object',
                    'properties' => [
                        'file' => ['type' => 'string'],
                        'line' => ['type' => 'number'],
                        'severity' => ['type' => 'string', 'enum' => ['low', 'medium', 'high']],
                        'description' => ['type' => 'string'],
                    ],
                    'required' => ['file', 'severity', 'description'],
                ],
            ],
            'total' => ['type' => 'number'],
        ],
        'required' => ['issues', 'total'],
    ]);

$result = ClaudeAgent::query('Find all TODO comments in src/', $options);
$data = $result->structured(); // Validated array matching schema

Session Resumption

Continue conversations across multiple queries:

// First query
$result = ClaudeAgent::query('Read the auth module');
$sessionId = $result->sessionId;

// Resume later with full context
$result2 = ClaudeAgent::query(
    'Now find all places that call it',
    ClaudeAgentOptions::make()->resume($sessionId),
);

// Fork a session to try different approaches
$result3 = ClaudeAgent::query(
    'Try refactoring it with a different pattern',
    ClaudeAgentOptions::make()->resume($sessionId, fork: true),
);

MCP Servers

Connect external tools via Model Context Protocol:

use ClaudeAgentSDK\Tools\McpServerConfig;

// Stdio transport
$options = ClaudeAgentOptions::make()
    ->mcpServer('database', McpServerConfig::stdio(
        command: 'npx',
        args: ['@modelcontextprotocol/server-database'],
        env: ['DB_URL' => config('database.url')],
    ))
    ->tools(['mcp__database__query', 'Read']);

$result = ClaudeAgent::query('Show me the latest users', $options);

// SSE transport
$options = ClaudeAgentOptions::make()
    ->mcpServer('api', McpServerConfig::sse(
        url: 'http://localhost:3000/mcp',
        headers: ['Authorization' => 'Bearer ' . config('services.mcp.token')],
    ));

// HTTP transport
$options = ClaudeAgentOptions::make()
    ->mcpServer('api', McpServerConfig::http(
        url: 'http://localhost:3000/mcp',
        headers: ['Authorization' => 'Bearer ' . config('services.mcp.token')],
    ));

Working with Results

$result = ClaudeAgent::query('Analyze this codebase');

// Basic info
$result->text();              // Final text result
$result->isSuccess();         // bool — true if subtype is 'success'
$result->isError();           // bool — true if error or no result
$result->costUsd();           // float|null — total cost in USD
$result->turns();             // int — number of conversation turns
$result->durationMs();        // int — total duration in milliseconds
$result->sessionId;           // string|null — for session resumption

// Error types
$result->subtype();              // 'success', 'error_max_turns', 'error_max_budget_usd', etc.
$result->isMaxTurnsError();      // true if stopped at turn limit
$result->isBudgetError();        // true if budget exceeded
$result->permissionDenials();    // array of denied tool uses
$result->errors();               // array of execution errors

// Session introspection
$result->model();                // Model used
$result->availableTools();       // Tools available
$result->mcpServerStatus();      // MCP server statuses
$result->supportedCommands();    // Available slash commands

// Messages
$result->messages;            // All Message objects
$result->assistantMessages(); // AssistantMessage[] only
$result->fullText();          // Concatenated text from all assistant messages
$result->toolUses();          // All ToolUseBlock objects across messages
$result->structured();        // Structured output array (if outputFormat set)

// Per-model usage & cache metrics
$result->modelUsage();        // array<string, ModelUsage> — per-model breakdown
$result->cacheReadTokens();   // int — total cache-read tokens across all models
$result->cacheCreationTokens(); // int — total cache-creation tokens

foreach ($result->modelUsage() as $model => $usage) {
    echo "{$model}: {$usage->inputTokens} in, {$usage->outputTokens} out\n";
    echo "  Cache hit rate: " . round($usage->cacheHitRate() * 100) . "%\n";
    echo "  Cost: \${$usage->costUsd}\n";
}

Working with Messages

use ClaudeAgentSDK\Messages\AssistantMessage;
use ClaudeAgentSDK\Messages\SystemMessage;
use ClaudeAgentSDK\Messages\ResultMessage;

foreach (ClaudeAgent::stream('Do something') as $message) {
    match (true) {
        $message instanceof SystemMessage => handleSystem($message),
        $message instanceof AssistantMessage => handleAssistant($message),
        $message instanceof ResultMessage => handleResult($message),
        default => null,
    };
}

function handleAssistant(AssistantMessage $msg): void
{
    // Text content
    echo $msg->text();

    // Tool calls made by Claude
    foreach ($msg->toolUses() as $toolUse) {
        echo "Tool: {$toolUse->name}, Input: " . json_encode($toolUse->input);
    }

    // Metadata
    echo $msg->model;            // Model used
    echo $msg->id;               // Message ID
    echo $msg->parentToolUseId;  // If this is a subagent response
}

Default Options via Config

Set defaults in config/claude-agent.php or .env:

CLAUDE_AGENT_MODEL=claude-sonnet-4-5-20250929
CLAUDE_AGENT_PERMISSION_MODE=acceptEdits
CLAUDE_AGENT_MAX_TURNS=10
CLAUDE_AGENT_MAX_BUDGET_USD=10.00
CLAUDE_AGENT_MAX_THINKING_TOKENS=8000
CLAUDE_AGENT_CWD=/var/www/project
CLAUDE_AGENT_TIMEOUT=300
CLAUDE_AGENT_CLI_PATH=/usr/local/bin/claude

Options passed to query() override config defaults.

Advanced: Sandbox, Plugins & Betas

// Run in a sandboxed environment with command isolation
$options = ClaudeAgentOptions::make()
    ->sandbox([
        'enabled' => true,
        'autoAllowBashIfSandboxed' => true,
        'excludedCommands' => ['docker'],
        'network' => [
            'allowLocalBinding' => true,
            'allowUnixSockets' => ['/var/run/docker.sock'],
        ],
    ]);

// Load a local plugin
$options = ClaudeAgentOptions::make()
    ->plugin('/path/to/my-plugin');

// Enable beta features
$options = ClaudeAgentOptions::make()
    ->betas(['context-1m-2025-08-07']);

// Set a fallback model
$options = ClaudeAgentOptions::make()
    ->model('claude-sonnet-4-5-20250929')
    ->fallbackModel('claude-haiku-4-5');

Stderr Monitoring

Capture diagnostic output from the Claude CLI process:

$options = ClaudeAgentOptions::make()
    ->stderr(function (string $data) {
        Log::warning('Claude CLI stderr: ' . $data);
    });

Interrupt & Lifecycle

Control a running Claude process programmatically:

$manager = app('claude-agent');
$manager->interrupt();    // Graceful interrupt (sends SIGINT to the process)
$manager->isRunning();    // Check if a process is currently running

Error Handling

use ClaudeAgentSDK\Exceptions\CliNotFoundException;
use ClaudeAgentSDK\Exceptions\ProcessException;
use ClaudeAgentSDK\Exceptions\JsonParseException;
use ClaudeAgentSDK\Exceptions\ClaudeAgentException;

try {
    $result = ClaudeAgent::query('Do something');
} catch (CliNotFoundException $e) {
    // Claude Code CLI not installed
    // $e->getMessage() includes install instructions
} catch (ProcessException $e) {
    echo $e->exitCode;   // Process exit code
    echo $e->stderr;     // Standard error output
} catch (JsonParseException $e) {
    echo $e->rawLine;    // The malformed JSON line
    echo $e->originalError; // The underlying JsonException
} catch (ClaudeAgentException $e) {
    // General SDK error
}

Testing

composer test

To run with coverage:

vendor/bin/phpunit --coverage-html coverage/

Support

If you find this package useful, please consider giving it a star on GitHub. It helps others discover the package and motivates continued development.

GitHub stars

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

MIT — see LICENSE for details.

Reviews (0)

No results found