A Laravel package that connects your AI agents to 250+ external tools (GitHub, Slack, Gmail, Notion, and more) via Composio. Works with both PrismPHP and Laravel AI.
Composio handles the hard parts: OAuth flows, credential management, API normalization, and rate limiting. This package converts Composio's tool definitions into native tool objects for your preferred AI framework, so your agents can call external APIs with a single line of code.
Your Laravel App
|
v
Composio Laravel --> converts to --> PrismPHP Tool objects
| or Laravel AI Tool objects
v
Composio API --> handles auth --> GitHub, Slack, Gmail, etc.
- You ask for tools (e.g. "give me GitHub tools")
- This package fetches tool schemas from Composio and converts them to PrismPHP or Laravel AI tool objects
- You pass those tools to your AI model
- When the AI calls a tool, Composio executes it against the real API using your user's connected account
- PHP 8.3+
- Laravel 12 or 13
- A Composio API key
composer require blockshiftnetwork/composio-laravelThen install whichever AI framework you want to use (or both):
# For PrismPHP
composer require prism-php/prism
# For Laravel AI
composer require laravel/aiAdd your API key to .env:
COMPOSIO_API_KEY=your-composio-api-keyThe package reads from config/services.php under the composio key. Defaults are provided via environment variables, so no config publishing is needed. To override values explicitly, add to your config/services.php:
'composio' => [
'api_key' => env('COMPOSIO_API_KEY'),
'base_url' => env('COMPOSIO_BASE_URL', 'https://backend.composio.dev'),
'default_user_id' => env('COMPOSIO_DEFAULT_USER_ID'),
'default_entity_id' => env('COMPOSIO_DEFAULT_ENTITY_ID'),
],use BlockshiftNetwork\ComposioLaravel\Facades\Composio;
use Prism\Prism\Prism;
// Get GitHub tools as PrismPHP Tool objects
$tools = Composio::toolSet(userId: 'user_123')
->getTools(toolkitSlug: 'github');
// Pass them to any LLM via PrismPHP
$response = Prism::text()
->using('anthropic', 'claude-sonnet-4-20250514')
->withTools($tools)
->withPrompt('Star the repo composio-dev/composio on GitHub')
->generate();use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use BlockshiftNetwork\ComposioLaravel\Facades\Composio;
class GitHubAssistant implements Agent, HasTools
{
public function prompt(): string
{
return 'You are a GitHub assistant. Help users manage issues and PRs.';
}
public function tools(): iterable
{
return Composio::toolSet(userId: 'user_123')
->getLaravelAiTools(toolkitSlug: 'github');
}
}Everything starts with a ToolSet. Create one via the facade:
use BlockshiftNetwork\ComposioLaravel\Facades\Composio;
// Basic toolset
$toolSet = Composio::toolSet();
// Toolset scoped to a specific user
$toolSet = Composio::toolSet(userId: 'user_123');
// Toolset with both user and entity
$toolSet = Composio::toolSet(userId: 'user_123', entityId: 'entity_456');Or via dependency injection:
use BlockshiftNetwork\ComposioLaravel\ComposioManager;
class MyService
{
public function __construct(private ComposioManager $composio) {}
public function doSomething()
{
$toolSet = $this->composio->toolSet(userId: 'user_123');
}
}$tools = $toolSet->getTools(toolkitSlug: 'github'); // PrismPHP
$tools = $toolSet->getLaravelAiTools(toolkitSlug: 'slack'); // Laravel AI$tools = $toolSet->getTools(toolSlugs: [
'GITHUB_CREATE_ISSUE',
'GITHUB_LIST_ISSUES',
'GITHUB_CLOSE_ISSUE',
]);$tools = $toolSet->getTools(toolkitSlug: 'github', tags: ['issues']);$tools = $toolSet->getTools(search: 'send email');$tool = $toolSet->getTool('GITHUB_CREATE_ISSUE'); // PrismPHP
$tool = $toolSet->getLaravelAiTool('SLACK_SEND_MESSAGE'); // Laravel AIEach user in your app can have their own connected accounts (e.g., their own GitHub OAuth token). Scope your toolset per-user so tool executions use the correct credentials.
// At creation time
$toolSet = Composio::toolSet(userId: 'user_123');
// Or fluently (returns a new immutable instance)
$toolSet = Composio::toolSet()
->forUser('user_123')
->withConnectedAccount('conn_abc');
// Scoping is immutable -- the original is unchanged
$base = Composio::toolSet();
$alice = $base->forUser('alice'); // new instance
$bob = $base->forUser('bob'); // new instance, $base unchangedExecute tools programmatically without going through an LLM:
$result = Composio::toolSet(userId: 'user_123')
->execute('GITHUB_CREATE_ISSUE', [
'owner' => 'myorg',
'repo' => 'myrepo',
'title' => 'Bug: login page returns 500',
'body' => 'Steps to reproduce: ...',
'labels' => ['bug', 'priority-high'],
]);
if ($result->isSuccessful()) {
$data = $result->data(); // array of response data
echo "Created issue #{$data['number']}";
} else {
echo "Error: " . $result->error(); // error message string
echo "Log ID: " . $result->logId(); // for debugging with Composio
}ExecutionResult methods:
| Method | Returns | Description |
|---|---|---|
isSuccessful() |
bool |
Whether the execution succeeded |
data() |
array |
The tool's response data |
error() |
?string |
Error message if execution failed |
logId() |
string |
Composio log ID for debugging |
toToolOutput() |
string |
JSON string (used internally by converters) |
raw() |
Response |
The raw API response object |
Hooks let you intercept tool executions to modify arguments, log activity, enforce permissions, or transform results.
Modify arguments before a tool runs:
$toolSet = Composio::toolSet(userId: 'user_123');
// Hook for a specific tool
$toolSet->hooks()->beforeExecute('GITHUB_CREATE_ISSUE', function (string $tool, array $args) {
$args['labels'] = array_merge($args['labels'] ?? [], ['auto-created']);
return $args; // must return the (modified) arguments array
});
// Hook for multiple tools
$toolSet->hooks()->beforeExecute(
['GITHUB_CREATE_ISSUE', 'GITHUB_UPDATE_ISSUE'],
function (string $tool, array $args) {
$args['_metadata'] = ['source' => 'ai-agent'];
return $args;
}
);Log or transform results after a tool runs:
// Wildcard '*' matches all tools
$toolSet->hooks()->afterExecute('*', function (string $tool, ExecutionResult $result) {
Log::info('Composio tool executed', [
'tool' => $tool,
'successful' => $result->isSuccessful(),
'log_id' => $result->logId(),
]);
return $result; // must return the (modified) result
});For reusable hooks, implement the interfaces:
use BlockshiftNetwork\ComposioLaravel\Hooks\BeforeExecuteHook;
use BlockshiftNetwork\ComposioLaravel\Hooks\AfterExecuteHook;
class AuditHook implements AfterExecuteHook
{
public function handle(string $toolSlug, ExecutionResult $result): ExecutionResult
{
AuditLog::create([
'tool' => $toolSlug,
'success' => $result->isSuccessful(),
'log_id' => $result->logId(),
]);
return $result;
}
}
$toolSet->hooks()->afterExecute('*', new AuditHook);Manage OAuth connections for your users:
use BlockshiftNetwork\ComposioLaravel\Facades\Composio;
// List all connected accounts
$accounts = Composio::connectedAccounts()->list();
// Filter by user
$accounts = Composio::connectedAccounts()->list(userIds: ['user_123']);
// Filter by toolkit
$accounts = Composio::connectedAccounts()->list(toolkitSlugs: ['github']);
// Get a specific connected account
$account = Composio::connectedAccounts()->get('connected_account_id');
// Refresh an account's credentials
Composio::connectedAccounts()->refresh('connected_account_id');
// Delete a connected account
Composio::connectedAccounts()->delete('connected_account_id');Manage authentication configurations (OAuth apps, API keys):
// List auth configs
$configs = Composio::authConfigs()->list();
// Filter by toolkit
$configs = Composio::authConfigs()->list(toolkitSlug: 'github');
// Get, update, delete
$config = Composio::authConfigs()->get('auth_config_id');
Composio::authConfigs()->delete('auth_config_id');Browse the catalog of integrations available on Composio:
use BlockshiftNetwork\ComposioLaravel\Facades\Composio;
// List toolkits (paginated)
$toolkits = Composio::toolkits()->list(category: 'productivity', limit: 20);
// Get a single toolkit
$github = Composio::toolkits()->get('github');
// List categories
$categories = Composio::toolkits()->categories();
// Changelog
$changelog = Composio::toolkits()->changelog();Triggers deliver events from external apps (new Slack message, new GitHub commit, incoming Gmail, etc.) to your application.
use BlockshiftNetwork\Composio\Model\PostTriggerInstancesBySlugUpsertRequest;
use BlockshiftNetwork\ComposioLaravel\Facades\Composio;
// Discover available trigger types
$types = Composio::triggers()->listTypes(toolkitSlugs: ['github']);
$type = Composio::triggers()->getType('GITHUB_PUSH_EVENT');
// Activate a trigger for a connected account
$request = new PostTriggerInstancesBySlugUpsertRequest;
$request->setConnectedAccountId('conn_abc');
$request->setTriggerConfig(['repository' => 'myorg/myrepo']);
$instance = Composio::triggers()->upsert('GITHUB_PUSH_EVENT', $request);
// List active instances
$instances = Composio::triggers()->listInstances(connectedAccountIds: ['conn_abc']);
// Pause / resume / delete
Composio::triggers()->disable('trigger_instance_id');
Composio::triggers()->enable('trigger_instance_id');
Composio::triggers()->delete('trigger_instance_id');This package does not ship a webhook controller. Composio webhooks are signed with HMAC-SHA256 in the webhook-signature header (format v1,<base64_signature>), and the cleanest way to handle them in Laravel is the spatie/laravel-webhook-client package, which gives you signature verification, queued processing, and replay protection out of the box.
Install Spatie's package:
composer require spatie/laravel-webhook-client
php artisan vendor:publish --provider="Spatie\WebhookClient\WebhookClientServiceProvider" --tag="webhook-client-config"Implement a SignatureValidator that verifies Composio's HMAC signature:
namespace App\Webhooks\Composio;
use Illuminate\Http\Request;
use Spatie\WebhookClient\SignatureValidator\SignatureValidator;
use Spatie\WebhookClient\WebhookConfig;
class ComposioSignatureValidator implements SignatureValidator
{
public function isValid(Request $request, WebhookConfig $config): bool
{
$header = $request->header('webhook-signature');
if (! $header || ! str_starts_with($header, 'v1,')) {
return false;
}
$provided = base64_decode(substr($header, 3));
$expected = hash_hmac('sha256', $request->getContent(), $config->signingSecret, true);
return hash_equals($expected, $provided);
}
}Wire it up in config/webhook-client.php:
'configs' => [
[
'name' => 'composio',
'signing_secret' => env('COMPOSIO_WEBHOOK_SECRET'),
'signature_header_name' => 'webhook-signature',
'signature_validator' => \App\Webhooks\Composio\ComposioSignatureValidator::class,
'webhook_profile' => \Spatie\WebhookClient\WebhookProfile\ProcessEverythingWebhookProfile::class,
'webhook_response' => \Spatie\WebhookClient\WebhookResponse\DefaultRespondsTo::class,
'webhook_model' => \Spatie\WebhookClient\Models\WebhookCall::class,
'process_webhook_job' => \App\Jobs\ProcessComposioWebhook::class,
],
],Register the route and write your job:
// routes/web.php
Route::webhooks('/webhooks/composio', 'composio');// app/Jobs/ProcessComposioWebhook.php
namespace App\Jobs;
use Spatie\WebhookClient\Jobs\ProcessWebhookJob;
class ProcessComposioWebhook extends ProcessWebhookJob
{
public function handle(): void
{
$payload = $this->webhookCall->payload;
// dispatch domain logic based on $payload['type'], $payload['data'], etc.
}
}Composio's webhook configuration UI lives at app.composio.dev. The signing secret you put in
COMPOSIO_WEBHOOK_SECRETmust match the one shown there. See the Using Triggers guide for the full event payload format.
Composio MCP generates a Model Context Protocol server URL bundling whichever toolkits and auth configs you choose. Drop the URL into Claude Desktop, Cursor, ChatGPT, or any MCP-compatible client and your toolkits become first-class tools for the LLM — no custom server to write.
use BlockshiftNetwork\Composio\Model\PostMcpServersCustomRequest;
use BlockshiftNetwork\ComposioLaravel\Facades\Composio;
// Create a custom MCP server bundling multiple toolkits
$request = new PostMcpServersCustomRequest;
$request->setName('engineering-assistant');
$request->setToolkits(['github', 'slack', 'linear']);
// $request->setAuthConfigIds(['ac_github_xxx', 'ac_slack_yyy']);
$server = Composio::mcp()->createCustomServer($request);
// Browse / fetch / update / delete
$servers = Composio::mcp()->list(name: 'engineering-assistant');
$server = Composio::mcp()->get('mcp_server_id');
Composio::mcp()->delete('mcp_server_id');
// Per-user instances of a server
$instances = Composio::mcp()->listInstances('mcp_server_id');
Composio::mcp()->deleteInstance('mcp_server_id', 'instance_id');List file artifacts that have been uploaded as inputs/outputs of tool executions:
$files = Composio::files()->list(toolkitSlug: 'github', limit: 50);Uploads themselves go through Composio's presigned URL flow at execution time and are not exposed as a separate SDK call. The
files()->list()endpoint is the read-side view.
Fetch the canonical list of all tool slugs available on Composio:
$enums = Composio::toolSet()->enums();Given a natural language description, ask Composio to produce the structured arguments for a specific tool:
$inputs = Composio::toolSet(userId: 'user_123')
->generateInputs('GITHUB_CREATE_ISSUE', 'Open an issue about the login redirect bug, label it priority-high');When Composio doesn't yet have a tool for the operation you need, you can proxy a raw HTTP request through any toolkit's authenticated connection:
use BlockshiftNetwork\Composio\Model\PostToolsExecuteProxyRequest;
$request = new PostToolsExecuteProxyRequest;
$request->setEndpoint('/repos/myorg/myrepo/labels');
$request->setMethod('GET');
// $request->setBody([...]);
// $request->setParameters([...]);
$response = Composio::toolSet(userId: 'user_123')
->withConnectedAccount('conn_github_abc')
->proxyExecute($request);Expose your own PHP closures as tools alongside the Composio catalog. They appear in getTools() / getLaravelAiTools() and run locally — no API call to Composio:
use BlockshiftNetwork\ComposioLaravel\Facades\Composio;
Composio::customTools()->register(
slug: 'INTERNAL_LOOKUP_USER',
description: 'Looks up a user from our internal database by email',
inputSchema: [
'type' => 'object',
'properties' => [
'email' => ['type' => 'string', 'description' => 'The email to search for'],
],
'required' => ['email'],
],
handler: function (array $args): array {
$user = \App\Models\User::where('email', $args['email'])->firstOrFail();
return ['id' => $user->id, 'name' => $user->name];
},
);
// Now it shows up alongside Composio tools
$tools = Composio::toolSet()->getTools(toolkitSlug: 'github');
// $tools contains GitHub tools + the INTERNAL_LOOKUP_USER local toolCustom tools fully participate in the hook system — beforeExecute and afterExecute hooks fire for them just like remote tools.
namespace App\Http\Controllers;
use BlockshiftNetwork\ComposioLaravel\Facades\Composio;
use Illuminate\Http\Request;
use Prism\Prism\Prism;
class AiController extends Controller
{
public function ask(Request $request)
{
$request->validate(['prompt' => 'required|string']);
$tools = Composio::toolSet(userId: (string) $request->user()->id)
->getTools(toolkitSlug: 'github');
$response = Prism::text()
->using('anthropic', 'claude-sonnet-4-20250514')
->withTools($tools)
->withMaxSteps(5)
->withPrompt($request->input('prompt'))
->generate();
return response()->json(['response' => $response->text]);
}
}$toolSet = Composio::toolSet(userId: 'user_123');
$tools = array_merge(
$toolSet->getTools(toolkitSlug: 'github', tags: ['issues']),
$toolSet->getTools(toolkitSlug: 'slack'),
$toolSet->getTools(toolkitSlug: 'gmail'),
);
$response = Prism::text()
->using('openai', 'gpt-4o')
->withTools($tools)
->withPrompt('Check my open GitHub issues, summarize them, and send a digest to #engineering on Slack')
->generate();namespace App\Ai\Agents;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use BlockshiftNetwork\ComposioLaravel\Facades\Composio;
class DevOpsAgent implements Agent, HasTools
{
public function __construct(private string $userId) {}
public function prompt(): string
{
return 'You manage GitHub issues and send Slack notifications for the engineering team.';
}
public function tools(): iterable
{
$toolSet = Composio::toolSet(userId: $this->userId);
return [
...$toolSet->getLaravelAiTools(toolkitSlug: 'github', tags: ['issues']),
...$toolSet->getLaravelAiTools(toolkitSlug: 'slack'),
];
}
}// Usage
use Laravel\Ai\Facades\Ai;
$response = Ai::agent(new DevOpsAgent(auth()->id()))
->ask('Create an issue for the login bug and notify #backend on Slack');// Use in a scheduled command, queue job, or webhook handler
$toolSet = Composio::toolSet(userId: 'bot_user');
// Create a GitHub issue
$issue = $toolSet->execute('GITHUB_CREATE_ISSUE', [
'owner' => 'myorg',
'repo' => 'myrepo',
'title' => 'Automated: daily health check failed',
'body' => 'The health check endpoint returned 503 at ' . now(),
]);
// Send a Slack message
$toolSet->execute('SLACK_SEND_MESSAGE', [
'channel' => '#alerts',
'text' => "Health check failed. Issue created: #{$issue->data()['number']}",
]);$toolSet = Composio::toolSet(userId: 'user_123');
// Log every tool execution
$toolSet->hooks()->afterExecute('*', function (string $tool, $result) {
Log::channel('composio')->info("Tool: {$tool}", [
'success' => $result->isSuccessful(),
'log_id' => $result->logId(),
]);
return $result;
});
// Prevent destructive actions in production
$toolSet->hooks()->beforeExecute(
['GITHUB_DELETE_REPO', 'SLACK_DELETE_CHANNEL'],
function (string $tool, array $args) {
if (app()->isProduction()) {
throw new \RuntimeException("Tool {$tool} is blocked in production");
}
return $args;
}
);
$tools = $toolSet->getTools(toolkitSlug: 'github');
// Hooks fire automatically when the AI calls any toolsrc/
ComposioServiceProvider.php # Registers bindings, merges config into services.composio
ComposioManager.php # Main entry point (Facade target)
ComposioToolSet.php # Fetches tools, converts, executes, scopes
Facades/Composio.php # Laravel Facade
ToolConverter/
ToolConverterInterface.php # Common interface for converters
PrismToolConverter.php # Composio Tool -> PrismPHP Tool
LaravelAiToolConverter.php # Composio Tool -> Laravel AI Tool
SchemaMapper.php # JSON Schema -> PrismPHP parameters
LaravelAiSchemaMapper.php # JSON Schema -> Laravel AI JsonSchema types
LaravelAi/
ComposioTool.php # Implements Laravel\Ai\Contracts\Tool
Execution/
ToolExecutor.php # Calls Composio API to execute tools
ExecutionResult.php # Typed wrapper around execution response
Hooks/
HookManager.php # Before/after hook registry and runner
BeforeExecuteHook.php # Interface for before-execution hooks
AfterExecuteHook.php # Interface for after-execution hooks
Auth/
ConnectedAccountManager.php # CRUD for connected accounts
AuthConfigManager.php # CRUD for auth configurations
Toolkits/
ToolkitManager.php # List/get toolkits, categories, changelog
Triggers/
TriggerManager.php # CRUD trigger types & instances, enable/disable
Mcp/
McpServerManager.php # Create custom MCP servers, manage instances
Files/
FileManager.php # List uploaded file artifacts
Tools/
CustomToolRegistry.php # Register local PHP closures as tools
CustomTool.php # Value object for a registered custom tool
Entities/
Entity.php # User-scoped convenience wrapper
Exceptions/
ComposioException.php # Base exception
AuthenticationException.php # Auth failures
ToolExecutionException.php # Tool execution failures
- Dual framework support: PrismPHP and Laravel AI are both optional. The package detects which is installed at runtime via
class_exists(). You can use either, both, or neither (direct execution only). - Immutable scoping:
forUser(),forEntity(), andwithConnectedAccount()return cloned instances, so the original toolset is never mutated. - Automatic pagination:
getTools()follows cursor-based pagination internally, returning all matching tools. - Hook wildcards: Use
'*'to match all tools, or target specific tool slugs.
composer test# Run all quality checks at once
composer quality
# Or individually
composer pint:check # Code style (Laravel Pint)
composer phpstan # Static analysis (PHPStan level 5)
composer rector:dry-run # Code modernization (Rector)
# Auto-fix code style
composer pint
# Auto-apply Rector fixes
composer rectorGitHub Actions runs automatically on push to main and on pull requests:
- Tests — PHP 8.3/8.4 with Laravel 12
- Code Quality — Pint, PHPStan, and Rector checks in parallel
MIT