ferdiunal / larapanda
This is my package larapanda
Requires
- php: ^8.5
- illuminate/contracts: ^11.0||^12.0||^13.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- spatie/laravel-ray: ^1.35
Suggests
- laravel/ai: Required for Laravel AI SDK tool adapters.
- laravel/mcp: Required for Laravel MCP server integration.
README
Language versions: English | Türkçe
Larapanda is a type-safe Lightpanda SDK for Laravel and plain PHP applications. It provides named instance profiles, runtime resolution (auto, cli, docker), and immutable method-scoped option objects for fetch, serve, and mcp.
Installation
Prerequisites
Larapanda uses a runtime resolver (auto, cli, docker). CLI execution (and auto mode when CLI is selected) requires a valid Lightpanda binary. Install Lightpanda from the official source: lightpanda.io.
curl -fsSL https://pkg.lightpanda.io/install.sh | bash
Install the Larapanda SDK package:
composer require ferdiunal/larapanda
For Laravel applications, publish the package configuration:
php artisan vendor:publish --tag="larapanda-config"
Optional integration dependencies:
composer require laravel/ai laravel/mcp
Configuration
Configuration is profile-based. Each client resolves an instance profile by name, and each profile may override the global defaults.
return [ 'default_instance' => 'default', 'defaults' => [ 'runtime' => 'auto', 'binary_path' => env('LARAPANDA_BINARY_PATH'), 'docker' => [ 'command' => 'docker', 'image' => 'lightpanda/browser:nightly', 'container_name' => 'larapanda-lightpanda', 'remove' => true, 'extra_args' => [], ], ], 'instances' => [ 'default' => [], 'crawler' => [ // For strict CLI mode: // 'runtime' => 'cli', // 'binary_path' => '/absolute/path/to/lightpanda', ], 'mcp' => [ // For strict Docker mode: // 'runtime' => 'docker', ], ], ];
default_instanceselects the instance profile used when no explicit profile name is provided.defaultsdefines baseline runtime settings shared by all instances unless overridden.instances.<name>contains per-profile overrides for runtime mode, binary path, Docker parameters, and process options.- In
autoruntime mode, Larapanda prefers CLI execution whenbinary_pathis available and executable; otherwise it falls back to Docker. integrations.aiconfigures AI SDK tool adapters (instance,tool_prefix,exposed_tools, session settings).integrations.mcpconfigures Laravel MCP server tool adapters (instance,exposed_tools, session settings).
Usage
Scenario 1: Quickstart Fetch
Resolve the manager, select an instance profile, and execute a markdown fetch.
use Ferdiunal\Larapanda\Contracts\LarapandaManagerInterface; use Ferdiunal\Larapanda\Enums\FetchDumpFormat; $manager = app(LarapandaManagerInterface::class); $client = $manager->instance('default'); $fetch = $client->fetchRequest('https://example.com') ->withOptions( dump: FetchDumpFormat::Markdown, obeyRobots: true, waitMs: 2000, ) ->run(); $markdown = $fetch->asMarkdown();
Scenario 2: Output Modes
Use strict typed accessors based on the selected dump format.
use Ferdiunal\Larapanda\Enums\FetchDumpFormat; $markdownResult = $client->fetchRequest('https://example.com') ->withOptions( dump: FetchDumpFormat::Markdown, ) ->run(); $semanticTreeResult = $client->fetchRequest('https://example.com') ->withOptions( dump: FetchDumpFormat::SemanticTree, ) ->run(); $semanticTreeTextResult = $client->fetchRequest('https://example.com') ->withOptions( dump: FetchDumpFormat::SemanticTreeText, ) ->run(); $markdown = $markdownResult->asMarkdown(); $semanticTree = $semanticTreeResult->asSemanticTree(); // array<string, mixed> $semanticTreeText = $semanticTreeTextResult->asSemanticTreeText(); $rawOutput = $semanticTreeTextResult->output(); // raw stdout fallback
FetchResult throws UnexpectedFetchOutputFormatException when a strict accessor does not match the selected dump format.
Scenario 3: Named Instance Profiles
Select the profile according to runtime intent:
default: baseline fetch workloads.crawler: stricter crawl profile (for example, dedicated CLI runtime settings).mcp: MCP-oriented profile for long-running interactive sessions.
$defaultClient = $manager->instance('default'); $crawlerClient = $manager->instance('crawler'); $mcpClient = $manager->instance('mcp');
Scenario 4: Proxy-Aware Fetch
Use request-level proxy settings for one fetch operation:
use Ferdiunal\Larapanda\Enums\FetchDumpFormat; $fetch = $client->fetchRequest('https://example.com') ->withOptions( dump: FetchDumpFormat::Markdown, httpProxy: 'http://127.0.0.1:3000', proxyBearerToken: 'MY-TOKEN', ) ->run();
Use integration-level proxy settings for AI SDK and MCP server tool sessions:
// config/larapanda.php 'integrations' => [ 'ai' => [ 'http_proxy' => 'http://127.0.0.1:3000', 'proxy_bearer_token' => 'MY-TOKEN', ], 'mcp' => [ 'http_proxy' => 'http://127.0.0.1:3000', 'proxy_bearer_token' => 'MY-TOKEN', ], ],
Scenario 5: Long-Running Serve and MCP Modes
Use RunningInstanceHandle lifecycle methods for process-safe execution:
$serveHandle = $client->serveRequest() ->withOptions(host: '127.0.0.1', port: 9222) ->run(); try { if ($serveHandle->isRunning()) { // connect with CDP client } } finally { $serveHandle->stop(); $serveHandle->wait(2.0); } $mcpHandle = $client->mcpRequest()->run(); try { if ($mcpHandle->isRunning()) { // attach MCP host/client } } finally { $mcpHandle->stop(); $mcpHandle->wait(2.0); }
Common Patterns
// Robots-compliant fetch $result = $client->fetchRequest('https://example.com') ->withOptions(obeyRobots: true, dump: FetchDumpFormat::Markdown) ->run(); // Strict accessor mismatch raises UnexpectedFetchOutputFormatException $result->asSemanticTreeText();
Laravel AI SDK Tools
Install optional dependencies before using adapter features:
composer require laravel/ai laravel/mcp
Larapanda exposes AI SDK-compatible tools via LarapandaAiTools. The adapter is MCP-backed, session-aware, and config-driven.
Scenario 1: Full Tool Catalog
use Ferdiunal\Larapanda\Integrations\Ai\LarapandaAiTools; use Illuminate\Support\Facades\AI; $response = AI::provider('openai') ->model('gpt-5-mini') ->prompt('Open laravel.com and return the main headings.') ->tools(app(LarapandaAiTools::class)->make()) ->text();
Tool naming uses the configured prefix (default lightpanda_), for example: lightpanda_markdown, lightpanda_semantic_tree, lightpanda_click.
Scenario 2: Restrict Exposed AI Tools
Limit model access to a subset of tool surfaces:
// config/larapanda.php 'integrations' => [ 'ai' => [ 'exposed_tools' => ['goto', 'markdown', 'semantic_tree'], ], ],
Scenario 3: Session-Aware AI Tool Continuity
For multi-step browsing tasks, guide the model to reuse a stable session_id across tool calls.
$response = AI::provider('openai') ->model('gpt-5-mini') ->prompt('Use lightpanda tools with session_id=\"docs-session\". First goto laravel.com, then return markdown.') ->tools(app(LarapandaAiTools::class)->make()) ->text();
Laravel MCP Server (Optional / Advanced)
Use this layer when you want Laravel-managed MCP registration, container wiring, config-based tool filtering, and shared session/proxy policies.
If you only need a standalone MCP binary host, native lightpanda mcp can be used directly without Larapanda adapter classes.
Native vs Adapter Decision Matrix
| Use case | Recommended path |
|---|---|
| Lowest-level MCP host integration over stdio | Native lightpanda mcp |
| Laravel container wiring + profile-based runtime resolution | Larapanda MCP adapter |
Config-driven tool exposure (integrations.mcp.exposed_tools) |
Larapanda MCP adapter |
| Shared session pool and proxy policy reused by AI SDK tools | Larapanda MCP adapter |
| Direct protocol troubleshooting against Lightpanda itself | Native lightpanda mcp |
Scenario 1: Register MCP Tools in routes/ai.php
use Ferdiunal\Larapanda\Integrations\Mcp\LarapandaMcpServer; LarapandaMcpServer::registerLocal(name: 'lightpanda');
Scenario 2: Restrict MCP Tool Exposure
By default, all Lightpanda MCP tools are exposed. To reduce tool surface:
// config/larapanda.php 'integrations' => [ 'mcp' => [ 'exposed_tools' => ['goto', 'markdown', 'semantic_tree'], ], ],
Scenario 3: Session and Proxy Policy for MCP Adapter
Session and proxy behavior are controlled from config:
- Each integration path (AI and MCP server) maintains an isolated in-memory session pool.
- Sessions map to long-running
lightpanda mcpprocesses. - Sessions expire using
session_ttl_secondsand are capped bymax_sessions. session_idcan be passed in tool arguments to preserve page context across calls.
// config/larapanda.php 'integrations' => [ 'mcp' => [ 'session_ttl_seconds' => 300, 'max_sessions' => 32, 'obey_robots' => true, 'http_proxy' => 'http://127.0.0.1:3000', 'proxy_bearer_token' => 'MY-TOKEN', ], ],
Scenario 4: Interactive MCP Argument Model (Native-Aligned)
Interactive tools use backend-node-driven arguments. The typical flow is:
- Navigate and discover target nodes (
goto+waitForSelectororinteractiveElements). - Extract
backendNodeIdfrom the response payload. - Execute interaction tools with that node id on the same
session_id.
// Example argument shapes (canonical tool contracts): // goto|navigate: ['url' => 'https://example.com', 'timeout' => 10000, 'waitUntil' => 'done'] // waitForSelector: ['selector' => '#submit', 'timeout' => 5000] // click|hover: ['backendNodeId' => 123] // fill: ['backendNodeId' => 123, 'text' => 'Ferdi'] // press: ['key' => 'Enter', 'backendNodeId' => 123] // backendNodeId optional // selectOption: ['backendNodeId' => 123, 'value' => 'tr'] // setChecked: ['backendNodeId' => 123, 'checked' => true] // scroll: ['y' => 400, 'backendNodeId' => 123] // backendNodeId optional
Testing
Run the full test suite:
composer test
Run opt-in live CLI + MCP smoke tests (native MCP and Larapanda bridge):
LIGHTPANDA_LIVE_TESTS=1 \ LIGHTPANDA_BINARY_PATH=/Users/ferdiunal/Web/larapanda/lightpanda \ php vendor/bin/pest --group=live
Run MCP-focused live smoke tests only:
LIGHTPANDA_LIVE_TESTS=1 \ LIGHTPANDA_BINARY_PATH=/Users/ferdiunal/Web/larapanda/lightpanda \ php vendor/bin/pest --filter=Mcp --group=live
Live suite policy:
- Live tests are opt-in and are not required in the default CI pipeline.
- In constrained environments, DNS/port limitations result in deterministic
skip, not flakyfail. - Real protocol and argument contract mismatches continue to fail.
Prerequisites for live tests:
LIGHTPANDA_BINARY_PATHpoints to a valid executable Lightpanda binary.- The environment permits internet access and local port binding.
- Optional proxy smoke test requires
LIGHTPANDA_HTTP_PROXY(and optionalLIGHTPANDA_PROXY_BEARER_TOKEN).
Optional proxy smoke run:
LIGHTPANDA_LIVE_TESTS=1 \ LIGHTPANDA_BINARY_PATH=/Users/ferdiunal/Web/larapanda/lightpanda \ LIGHTPANDA_HTTP_PROXY=http://127.0.0.1:3000 \ LIGHTPANDA_PROXY_BEARER_TOKEN=YOUR_TOKEN \ php vendor/bin/pest --group=live
Credits
License
This package is licensed under the MIT License. See LICENSE.md for details.
