onetracepro/onetrace-laravel
OneTrace for Laravel: a lightweight integration that connects the OneTrace tracing and diagnostics service to your Laravel app, with auto-discovered service providers and sensible defaults for adding request-level visibility with minimal configuration.
Update the dependency and let the new artifacts publish:
composer require onetrace/onetrace-php:^1.13 # hard requirement for v1.10.0
composer update onetrace/onetrace-laravel
php artisan vendor:publish --tag=onetrace-config
Three things to look at first, in this order:
config/onetrace.php — the new store_mcp block (enabled, key, tools) and the existing cdp block.StoreMcpHandlers — the abstract class shipped by the package. This is where your store's capabilities live; everything else is wiring.routes/onetrace.php (or php artisan route:list --path=onetrace) — confirm POST /onetrace/mcp registered and sits outside the web middleware group.Then flip it on:
ONETRACE_STORE_MCP=true
ONETRACE_STORE_MCP_KEY= # leave blank, generate below
php artisan onetrace:store-mcp --key # prints a fresh key, persists it
php artisan onetrace:store-mcp # registers/connects this server with the bot
First use case: expose a read-only getOrderStatus tool. It is the smallest end-to-end slice — it exercises the handshake, the handler lookup, the tool schema, and the bot's response rendering without touching money or customer data. Get that answering in the chat widget before you add anything that mutates state.
Don't forget the Blade side, which is a separate switch from the MCP server:
@onetrace
That emits the tracker (1.16+) and calls cdp.chatCustomer() so the bot knows who is signed in. Without it your handlers receive an anonymous context and findCustomer() will have nothing to resolve.
StoreMcpHandlersEach public tool is a method with a typed signature; the package derives the JSON schema from it. Keep one handler class per domain (catalog, orders, account) and register them in a service provider:
final class OrderMcpHandlers extends StoreMcpHandlers
{
public function getOrderStatus(string $orderNumber): array
{
$order = Order::where('number', $orderNumber)->firstOrFail();
return ['status' => $order->status, 'eta' => $order->eta?->toDateString()];
}
}
Register in AppServiceProvider::boot() alongside any other handlers. Read tools are exposed automatically; anything with side effects must be opted in — see below.
The default posture is read-only, which is the right default for a chat surface. An action tool is only advertised to the bot when its name appears in config:
// config/onetrace.php
'store_mcp' => [
'tools' => ['cancelOrder', 'resendInvoice'],
],
Treat this list as your blast radius. Anything not listed is invisible to the model — a useful safety property, but also the most common "why isn't my tool being called" cause.
For destructive actions the package gives you the flow you'd otherwise hand-roll: findCustomer() resolves the customer from the MCP context, sendCode() mints a code, stores it in the cache, and emails it. Your handler then takes the code as an argument and verifies before acting.
public function cancelOrder(string $orderNumber, string $code): array
{
$this->assertCode($code); // your verification wrapper
// ... cancel
return ['cancelled' => true, 'refund_eta' => '3-5 business days'];
}
Two-turn UX: the bot calls a read tool to confirm intent, then an action tool that triggers sendCode(), then the customer pastes the code back within the TTL. Model the tool schema so the confirmation code is a required parameter on the action — otherwise the model will happily call it without one.
@onetracePut @onetrace in your authenticated layout only. It emits tracker 1.16 and the CDP call that binds the session to the signed-in customer, which is what makes findCustomer() return anything. For guest carts, fall back to whatever identifier your handler accepts and validate it yourself.
Run php artisan onetrace:store-mcp once per environment as part of provisioning, not on every deploy. Use --disconnect before tearing an environment down or rotating the endpoint, and --key when you need to rotate — the old key stops working immediately.
The endpoint is deliberately session-free and CSRF-free. It authenticates by key alone. That means auth()->user() is null inside your handlers, and therefore so is $request->user(). Everything must come through findCustomer() or explicit arguments. Don't try to bolt auth middleware onto the route — you'll break the handshake and get an opaque 419/401 from the bot side.
Treat findCustomer() output as untrusted input from the model's perspective. The bot decides which identifier to send. Never let a handler act on a customer id supplied inline by the model when findCustomer() can resolve it — that's a straightforward IDOR otherwise. Scope every query by the resolved customer, not by an argument.
Action tools silently no-op. If a tool is defined but not in store_mcp.tools, the bot simply doesn't know it exists. There is no error, no log line. When a tool "isn't firing," check config before you check the handler.
Cache driver matters for confirmation codes. sendCode() stores in the cache. If you run multiple app servers on array or file, the code issued on server A won't verify on server B. Use Redis/database, and confirm the TTL matches what you tell customers in the email copy.
Config caching. ONETRACE_STORE_MCP and the key are read through config. Changing .env on a cached-config deploy does nothing until php artisan config:clear. This burns people during the first rollout.
Pin onetrace-php to ^1.13. v1.10.0 requires it; older versions will install fine and then fail at runtime inside cdp.chatCustomer() or the MCP contract.
Tracker 1.16 is a floor, not a suggestion. The @onetrace directive depends on it for the customer-passing call. If you've pinned the JS asset version anywhere (CSP, asset pipeline, a hardcoded CDN URL), bump it deliberately rather than relying on the directive.
Debugging: the endpoint is stateless, so there's no session to inspect. Log inside your handlers — tool name, resolved customer, outcome — and correlate with the bot's request id if the package forwards one. Test the contract directly before blaming the bot:
curl -X POST https://your-app.test/onetrace/mcp \
-H "Authorization: Bearer $ONETRACE_STORE_MCP_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
That single call answers "is the server up, is the key right, is my handler registered, is my tool in the list" in one shot.
Extension points worth knowing: the handler classes for domain separation, store_mcp.tools for per-environment capability gating (expose actions in staging, read-only in production until you trust the flow), and the artisan command for environment lifecycle. There's no event system here — if you need audit trails on action tools, write them in the handler.
How can I help you explore Laravel packages today?