jelte-ten-holt / in-other-agents
MCP (Streamable HTTP) scaffolding for Laravel apps — AgentTool base class, bearer + OAuth 2.1 (Passport) auth with RFC 7591 Dynamic Client Registration, tool registry, audit log subscriber.
Requires
- php: ^8.3
- laravel/framework: ^12.0 || ^13.0
- opgginc/laravel-mcp-server: ^2.0
Requires (Dev)
- laravel/passport: ^13.0
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0 || ^11.0
- phpunit/phpunit: ^11.0 || ^12.0
Suggests
- laravel/passport: Required when agents.auth.oauth.enabled is true — Passport powers the authorization server backing the OAuth flow (^13.0).
This package is auto-updated.
Last update: 2026-05-20 13:10:14 UTC
README
MCP (Streamable HTTP) scaffolding for Laravel apps. Provides:
AgentToolabstract base — the single point of coupling toopgginc/laravel-mcp-serverAuthenticateAgentmiddleware — bearer and OAuth 2.1 (Passport) token check, request stamping for log correlation- OAuth discovery + RFC 7591 Dynamic Client Registration so Co-work / web MCP clients can self-onboard
ToolRegistry— resolves the consumer'sconfig('agents.tools')listAgentLogSubscriber— audit logging of tool invocations and DCR registrations to a Monolog channel- Service provider wiring the
/mcproute, OAuth routes, middleware, and log subscriber
This package ships zero tools of its own. Consumers register everything via config/agents.php.
Install
composer require jelte-ten-holt/in-other-agents
Laravel package discovery picks up the service provider automatically.
Publish the config (optional — defaults work out of the box):
php artisan vendor:publish --tag=agents-config
Set the bearer token in .env:
AGENT_BEARER_TOKEN=some-long-random-string
An empty token fails closed — the /mcp endpoint rejects everything until a token is set.
Defining a tool
Create a class that extends InOtherAgents\AgentTool:
namespace App\Mcp\Tools; use InOtherAgents\AgentTool; final class Ping extends AgentTool { public static function identifier(): string { return 'ping'; } public static function displayName(): string { return 'Ping'; } public function description(): string { return 'Health check — returns pong.'; } public function inputSchema(): array { return [ 'type' => 'object', 'properties' => new \stdClass(), ]; } public function __invoke(array $arguments): array { return ['result' => 'pong']; } }
Register it in config/agents.php:
'tools' => [ App\Mcp\Tools\Ping::class, ],
Then POST to /mcp with Authorization: Bearer <token> and a standard MCP JSON-RPC envelope.
Audit log
Every tool invocation dispatches InOtherAgents\Events\ToolInvoked (success) or ToolInvocationFailed (throw). The bundled AgentLogSubscriber writes them to the configured Monolog channel (AGENT_LOG_CHANNEL, defaults to stack). Bearer tokens are hashed before logging — never the raw value.
Errors thrown as InvalidArgumentException are translated to JSON-RPC INVALID_PARAMS so the calling agent sees the actual message. Other throwables propagate as the framework's INTERNAL_ERROR.
OAuth (since 0.2.0)
Bearer remains the default. To unlock OAuth for Co-work / web MCP clients:
composer require laravel/passportphp artisan passport:install(generates encryption keys + initial clients)- Configure the
apiguard inconfig/auth.php:'guards' => [ 'api' => ['driver' => 'passport', 'provider' => 'users'], ],
- In
.env:AGENT_OAUTH_ENABLED=true AGENT_CANONICAL_URL=https://your-app.example.com/mcp
What you get:
POST /oauth/register— RFC 7591 Dynamic Client Registration. Co-work posts here on first connect when the connector form is left with blank OAuth fields. Open by default; setAGENT_DCR_INITIAL_ACCESS_TOKENto gate it.GET /.well-known/oauth-authorization-server— RFC 8414 discovery doc.GET /.well-known/oauth-protected-resource— RFC 9728 doc. Advertised inWWW-Authenticateon every 401 from/mcp.- RFC 8707
resourceparameter enforcement on every Passport route (off by default — flipAGENT_OAUTH_REQUIRE_RESOURCE=trueonce every known client sends it). - Scope-gated access (
agentbase,agent.adminelevated). Static bearer holders are admin by construction; that path is untouched.
End-to-end OAuth flow is verified via the consumer app's Passport setup; the package's test suite covers the metadata endpoints, resource enforcement, and the bearer path.
Versioning
Semantic Versioning. Pre-1.0, the surface may shift between minor versions while consumers iterate.