Resources
Deploy the MCP server
Configure the Laravel backend that runs your MCP tools. This is separate from hosting this static documentation website.
This guide prepares the existing Laravel application for a remote MCP endpoint and public documentation. It does not provision DNS, change Coolify or claim that the live service has been deployed. The code ships with MCP disabled until you deliberately enable it.
Use this alongside the main MCP guide and the architecture map. The repository also contains the general deployment guide at docs/coolify-deployment.md; that broader application guide is not part of this portable MCP bundle.
1. Domains and processes
Use the same Laravel/Coolify application for these hosts:
| Public URL | Purpose |
|---|---|
https://app.caroush.com | Existing application, login, OAuth consent and request approval |
https://mcp.caroush.com/mcp | Remote MCP resource endpoint |
https://api.caroush.com/developers/mcp | Public developer documentation; the configured documentation host also serves the reference at its root |
https://caroush.com | Existing separate marketing site; no change needed |
Point the new subdomains to the existing VPS/proxy and attach them to the same Coolify application. Enable valid HTTPS certificates for every advertised host. Keep APP_URL=https://app.caroush.com: the MCP resource and documentation host have their own configuration variables.
The MCP route uses Laravel/PHP-FPM. No extra server process, MCP container or port is required. Keep the current deployment mode:
| Existing build mode | Internal HTTP port | Startup file |
|---|---|---|
Dockerfile.coolify | 8080 | docker/entrypoint.sh |
| Nixpacks | 80 | docker/nixpacks-start.sh, configured in nixpacks.toml |
Both supplied startup files already run migrations/caches before starting the web server, queue workers and scheduler. Keep Coolify command overrides empty unless your installation has an independently reviewed reason to override them. Do not start php artisan serve in production.
2. Runtime environment settings
Keep production secrets in Coolify runtime environment variables or securely mounted secret files. The private .env.production is a local configuration reference, not a file to publish in Git. Keep development credentials in the private local .env.
New MCP configuration:
CAROUSH_MCP_ENABLED=false
CAROUSH_MCP_URL=https://mcp.caroush.com/mcp
CAROUSH_MCP_ISSUER=https://app.caroush.com
CAROUSH_API_DOCS_URL=https://api.caroush.com/developers/mcp
CAROUSH_MCP_DCR_ENABLED=true
CAROUSH_MCP_ALLOWED_ORIGINS=| Setting | Meaning |
|---|---|
CAROUSH_MCP_ENABLED | Defaults to false. Set true after migrations, signing keys and host routing are ready. Disabling hides protocol and OAuth endpoints; the settings/docs pages remain available. |
CAROUSH_MCP_URL | Complete canonical resource URL, including /mcp. The current route is /mcp; changing this value does not create a different route path. |
CAROUSH_MCP_ISSUER | Authorization server origin. Use the application host so existing login sessions work for consent. |
CAROUSH_API_DOCS_URL | Link users see for documentation. The existing public route is /developers/mcp. |
CAROUSH_MCP_DCR_ENABLED | Enables public MCP client registration. Keep enabled for clients that register dynamically. Disabling prevents new dynamic registrations; it is not a token-revocation switch. |
CAROUSH_MCP_ALLOWED_ORIGINS | Optional comma-separated exact additional browser origins accepted by the endpoint's Origin and CORS checks. Resource and issuer origins are already recognized. Preflight requests are supported on the protocol, token, registration and revocation routes. Avoid wildcard origins. |
No global Caroush MCP API key is needed. Each user authorizes a registered client and workspace. Do not put AI provider keys, social tokens or APP_KEY into MCP client configuration.
Preserve these existing application settings:
APP_ENV=production
APP_DEBUG=false
APP_URL=https://app.caroush.com
CAROUSH_NATIVE=true
DB_QUEUE_RETRY_AFTER=1900Leave the established database, Stripe, mail, GCS, Sentry and AI provider configuration intact. Existing Redis cache/session configuration can stay as it is. MCP background work deliberately uses the database queue even if another default queue connection is configured.
3. Create and retain OAuth signing keys once
Passport requires an RSA private/public key pair to issue and validate tokens. Its defaults are:
storage/oauth-private.key
storage/oauth-public.keyFor a fresh local development setup, generate them once from the Laravel project root:
php artisan passport:keysDo not run this repeatedly or use --force during routine deployments. Existing access tokens rely on stable signing keys. Keep signing keys separate between local development and production.
For production, choose one of these supported storage approaches:
- Coolify runtime secrets: generate a production key pair in a trusted private environment, then store its PEM contents as multiline
PASSPORT_PRIVATE_KEYandPASSPORT_PUBLIC_KEYvalues in Coolify. The private key is secret. Do not paste either value into public documentation or chat. Passport already reads these variables; there is no need to publish its config just to enable them. - Secret file mounts: securely mount the production key files at the default paths inside the application container. For Docker these are
/var/www/html/storage/oauth-private.keyand/var/www/html/storage/oauth-public.key; for Nixpacks they are/app/storage/oauth-private.keyand/app/storage/oauth-public.key. Make the files readable by the actual PHP/worker user and protect the private key from other users.
The existing deployment persists storage/app, not the whole storage directory. Generating default key files inside an ephemeral container without a secret mount will lose them at the next replacement. Use runtime secrets or dedicated secret mounts; do not mount over the whole repository or public directory to preserve them.
The repository ignores storage/*.key. Signing keys, private environment files and tokens must stay excluded from Git and image build layers. Provider/application secrets are required at runtime, not to compile the image.
Preserve the existing APP_KEY too. It encrypts social credentials and MCP operation arguments/results. Changing it is not a redeployment step and can make stored data unreadable.
4. Apply the additive migrations
The MCP feature adds:
2026_09_28_210000_create_mcp_oauth_tables.php: Passport clients, authorization codes, access-token and refresh-token records, workspace grants and browser-consent nonce records.2026_09_28_211000_create_mcp_operations_tables.php: idempotent operation records and metadata-only audit events.
Back up the production database before a release with schema changes. With the supplied Docker/Nixpacks startup, pending migrations run automatically through docker/migrate.php, which takes a PostgreSQL advisory lock and invokes:
php artisan migrate --force --no-interactionThat applies only migrations not previously run. It does not drop data. Never use migrate:fresh, migrate:refresh, db:wipe or deployment seeding against the live database.
If CAROUSH_AUTO_MIGRATE=false is an intentional operational choice, apply migrate --force once through your release procedure before starting workers. Do not add a second migration job if automatic migration is enabled.
Inspect installed migrations without modifying data:
php artisan migrate:statusThe new tables must use the same application database, not a newly created empty database. The MCP installation does not copy data from your laptop to production.
5. Retain the existing queue workers and scheduler
MCP long operations and browser-approved actions use:
Connection: database
Queue: caroush
Worker timeout: 1800 seconds
database.retry_after: 1900 secondsBoth supplied Supervisor configurations already include the worker:
php -d memory_limit=1G artisan queue:work database --queue=caroush,default --sleep=1 --tries=1 --timeout=1800 --memory=1024 --no-interactionDB_QUEUE_RETRY_AFTER=1900 must be longer than the worker timeout. The default Laravel value of 90 seconds is unsuitable for this worker and may allow another worker to claim unfinished work.
Native generation keeps its existing workers:
| Work | Connection/queue | Worker timeout | Retry-after |
|---|---|---|---|
| General/MCP/carousel generation | database, caroush,default | 1800 seconds | 1900 seconds |
| Video automation, including UGC | video-automation, video-automation | 2400 seconds | 2700 seconds |
| Carousel automation | carousel-automation, carousel-automation | 1800 seconds | 2100 seconds |
| Transactional mail | mail, mail | 60 seconds | 120 seconds |
The existing scheduler checks publication and automation work every minute. Analytics dispatch runs at midnight New York time by default. MCP does not require a separate cron entry.
If you operate outside the supplied container supervisor, a normal Laravel scheduler entry is:
* * * * * cd /path/to/caroush && php artisan schedule:run >> /dev/null 2>&1Use the real application path/PHP executable for that environment. Do not add this host cron when the application container already runs its scheduler. Keep one effective scheduler for this deployment setup and the existing graceful shutdown settings so active work can drain.
Check Supervisor in Coolify's container terminal:
supervisorctl -s unix:///tmp/caroush-supervisor.sock statusExpect the web processes, queue-caroush, queue-mail, queue-video-automation, queue-carousel-automation and scheduler to remain running. Queue success is separate from HTTP health.
6. First release with MCP
- Keep
CAROUSH_MCP_ENABLED=falsewhile applying the release. - Configure the new DNS/HTTPS hosts on the existing Coolify resource.
- Install stable production Passport keys as runtime secrets or secure file mounts.
- Preserve the existing live database and application secrets; set
DB_QUEUE_RETRY_AFTER=1900. - Deploy the source and current compiled frontend using the established build mode. Composer must install from the committed lockfile. The Dockerfile uses committed
public/build; Nixpacks uses its existing build process. - Confirm the startup logs show successful pending migrations, cached configuration/routes/views and running workers.
- Confirm
/up,/loginand/developers/mcprespond on the intended hosts. - Set
CAROUSH_MCP_ENABLED=truein Coolify runtime configuration and redeploy/restart through the established application lifecycle so cached configuration and workers use the new value. - Perform the read-only discovery and user authorization checks below.
Do not point the frontend marketing deployment at the MCP path. These endpoints belong to the Laravel application. For the Docker build, keep the documentation files copied into the image because the public documentation controller reads the maintained Markdown guides.
7. Read-only production smoke checks
Check protected-resource metadata on the resource host:
curl -i https://mcp.caroush.com/.well-known/oauth-protected-resource/mcpExpect JSON declaring resource=https://mcp.caroush.com/mcp and authorization_servers containing https://app.caroush.com.
Check authorization metadata on the issuer:
curl -i https://app.caroush.com/.well-known/oauth-authorization-serverExpect the application issuer, /mcp/oauth/authorize, /mcp/oauth/token, supported code/refresh grants and S256.
An unauthenticated protocol request should be refused, not redirect to a browser login page:
curl -i -X POST https://mcp.caroush.com/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
--data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"caroush-smoke-check","version":"1.0"}}}'With MCP enabled and the signing configuration ready, expect 401 and a WWW-Authenticate header with the protected-resource metadata URL. Disabled MCP returns 404. Use a real OAuth-capable client for the authenticated handshake rather than copying access tokens into shell history.
Open the documentation:
https://api.caroush.com/developers/mcp
https://api.caroush.com/developers/mcp/catalog.json
https://api.caroush.com/developers/mcp/guide.md
https://api.caroush.com/developers/mcp/download.zipThe ZIP contains a portable HTML reference, the generated JSON tool/schema catalog and the Markdown implementation/deployment guides. It can be served as a separate static documentation site without application credentials. When the documentation hostname differs from the app and MCP hostnames, its / route serves the same reference.
Then authorize a test user through /settings/integrations/mcp and the client's connection flow. Start with read scopes. Confirm tool discovery, profile, the authorized workspace, connected-account DTOs and saved analytics where the subscription allows them. Revoke the connection from Caroush and confirm subsequent calls are rejected. Only run credit-spending or publication tests after deliberately approving those individual actions.
8. Local setup and verification
Run from the Laravel project root. Keep the local .env pointed at the intended local database and preserve its existing data.
CAROUSH_MCP_ENABLED=true
CAROUSH_MCP_URL=http://127.0.0.1:8000/mcp
CAROUSH_MCP_ISSUER=http://127.0.0.1:8000
CAROUSH_API_DOCS_URL=http://127.0.0.1:8000/developers/mcp
CAROUSH_MCP_DCR_ENABLED=true
CAROUSH_MCP_ALLOWED_ORIGINS=
DB_QUEUE_RETRY_AFTER=1900Use the same local host and port throughout the client configuration and resource parameter. Herd can serve a different local hostname; update all three URLs to match it. HTTPS and localhost redirect URIs, including ports, must exactly match registration. Numeric HTTP loopback redirects (127.0.0.1 and [::1]) permit variable ports under RFC 8252, but code exchange must use the exact redirect used during authorization.
Prepare dependencies, keys and schema as needed:
composer install
php artisan passport:keys
php artisan migrate
php artisan optimize:clearRun passport:keys only when the local key pair does not already exist. migrate applies pending local migrations, without a reset. If Herd already serves this application, use that site. Otherwise a local-only server is:
php artisan serve --host=127.0.0.1 --port=8000For local MCP long-running and approved actions, run the database worker shown in section 5. Testing native automation execution additionally requires its dedicated worker. Use your existing local provider-sandbox settings; a local endpoint does not by itself make provider requests harmless.
The automated suites use isolated in-memory SQLite with outbound provider requests prevented or faked:
php vendor/bin/phpunit --configuration=phpunit.caroush.xml --filter Mcp
php vendor/bin/phpunit --configuration=phpunit.caroush.xml tests/Caroush/PublishNowResponseTest.php tests/Caroush/CarouselPublishingSafetyTest.php tests/Caroush/CarouselAutomationConfigurationTest.php tests/Caroush/VideoAutomationConfigurationTest.php tests/Caroush/UgcAutomationConfigurationTest.php
php artisan route:list --path=mcpFor frontend edits, run the repository's existing TypeScript/lint/build checks. Automated tests do not establish that your production DNS, OAuth clients or provider permissions are configured; verify those separately.
Verification performed for this implementation
- The final isolated PostgreSQL run passed all 113 MCP tests / 1,587 assertions, covering OAuth, protocol, execution, content, operations, documentation and approval review. The temporary database was removed and the existing application user count was unchanged.
- A broader targeted run passed 235 tests / 2,602 assertions, including native publishing, automation configuration, products and Sentry regressions. Additional targeted checks covered native authentication, onboarding and analytics authentication. These runs overlap; their totals should not be added together as unique test coverage.
- TypeScript checking, scoped frontend lint/format checks, the production frontend build, Composer validation, PHP formatting, route caching and deployment-script checks passed.
- Local browser checks covered documentation search/schema expansion and desktop/mobile connection and approval views. Approval previews include owned media, captions, destinations, schedule details and automation settings.
Provider calls were faked in automated tests. No live social post, paid AI generation, production migration or production deployment was performed. A complete repository-wide rendering test run was not completed; the targeted regression suites above are the verified coverage. Production client authorization and provider permissions still require the smoke checks in section 7.
9. Routine redeployment
A normal redeploy keeps the same database, APP_KEY, Passport signing keys, GCS configuration and OAuth issuer/resource URLs. The supplied startup applies new migrations, rebuilds config/route/view caches and starts fresh workers. No separate artisan serve, manual cache sequence or extra cron is needed for each deployment.
Changing the canonical MCP resource URL invalidates existing workspace grants bound to the old resource, so users must reconnect. Treat issuer/resource changes and signing-key rotation as planned configuration changes, not routine release commands.
If you manually update source in a long-running environment that does not restart workers, follow that environment's release procedure to refresh cached configuration and restart workers. A new PHP file on disk is not proof that a long-running queue worker loaded it.
To stop new MCP access, set CAROUSH_MCP_ENABLED=false and redeploy with updated runtime configuration. To revoke a particular user's connection, use the integration settings page. Do not delete OAuth tables or clear the application database.
10. Troubleshooting
| Symptom | Check |
|---|---|
/mcp returns 404 | Enable flag, canonical resource host, proxy routing and cached runtime configuration. Production protocol requests must use the configured resource host. |
| Invalid key / cannot sign token | Passport PEM formatting, missing secret mount, file permissions and whether PHP/worker processes received the same runtime keys. |
| Every deployment disconnects clients | Keys were regenerated or existed only in the old container; retain the same signing pair and canonical resource URL. |
OAuth invalid_target | resource must exactly equal advertised CAROUSH_MCP_URL, including /mcp, scheme and port. |
| OAuth callback rejected | HTTPS and localhost callbacks require exact registration; numeric HTTP loopback ports can vary under RFC 8252. Code exchange must match authorization exactly. Do not use wildcard callback URLs. |
| Consent page repeatedly returns to login | Use the issuer on the application login host, correct session/secure-cookie configuration and trusted HTTPS proxy headers. |
| Origin rejected | Check the exact Origin and CAROUSH_MCP_ALLOWED_ORIGINS. This is separate from OAuth. Native/server clients commonly send no Origin; browser clients require their exact origin to be allowed. Verify that the proxy preserves the supplied CORS/preflight response headers. |
approval_required persists | Open the returned approval URL while signed in as that user and approve the stored action. Approval cannot be supplied in tool text. |
| Operation stays queued | queue-caroush must consume database caroush; verify Supervisor, database availability and queue configuration. |
| Native automation stays queued | Check the dedicated video/carousel automation worker, runtime configuration and native run details. |
outcome_unknown | Inspect saved content, schedules and the social platform before trying a new request. Blind retries may repeat an external effect. |
insufficient_scope | Reconnect and explicitly grant the requested scope. Existing tokens cannot acquire new permissions from a tool argument. |
| Analytics missing or forbidden | Pro entitlement, confirmed Caroush publication, latest daily sync, platform analytics permissions and provider retention limits. Unsupported metrics stay unavailable. |
| TikTok publication rejected by the tool | Complete the post-specific Caroush review composer. MCP cannot bypass TikTok's preview/settings/consent step. |
| Documentation missing in Docker | Include the maintained docs/ files in the image; verify /developers/mcp and catalog.json on the docs host. |
Use request IDs/operation IDs to correlate Caroush's MCP audit events and existing Sentry errors. Do not attach private environment files, provider tokens or full authorization headers to support logs.