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.
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 | bashInstall the Larapanda SDK package:
composer require ferdiunal/larapandaFor Laravel applications, publish the package configuration:
php artisan vendor:publish --tag="larapanda-config"Optional integration dependencies:
composer require laravel/ai laravel/mcpConfiguration 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).
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();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 fallbackFetchResult throws UnexpectedFetchOutputFormatException when a strict accessor does not match the selected dump format.
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');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',
],
],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);
}// Robots-compliant fetch
$result = $client->fetchRequest('https://example.com')
->withOptions(obeyRobots: true, dump: FetchDumpFormat::Markdown)
->run();
// Strict accessor mismatch raises UnexpectedFetchOutputFormatException
$result->asSemanticTreeText();Install optional dependencies before using adapter features:
composer require laravel/ai laravel/mcpLarapanda exposes AI SDK-compatible tools via LarapandaAiTools. The adapter is MCP-backed, session-aware, and config-driven.
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.
Limit model access to a subset of tool surfaces:
// config/larapanda.php
'integrations' => [
'ai' => [
'exposed_tools' => ['goto', 'markdown', 'semantic_tree'],
],
],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();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.
| 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 |
use Ferdiunal\Larapanda\Integrations\Mcp\LarapandaMcpServer;
LarapandaMcpServer::registerLocal(name: 'lightpanda');By default, all Lightpanda MCP tools are exposed. To reduce tool surface:
// config/larapanda.php
'integrations' => [
'mcp' => [
'exposed_tools' => ['goto', 'markdown', 'semantic_tree'],
],
],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',
],
],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 optionalRun the full test suite:
composer testRun 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=liveRun 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=liveLive 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=liveThis package is licensed under the MIT License. See LICENSE.md for details.
