---
title: "Deploying the MCP Server | DreamFactory Docs"
source: "https://docs.dreamfactory.com/AI/mcp-server-deployment"
canonical_url: "https://docs.dreamfactory.com/AI/mcp-server-deployment"
converted_at: "2026-09-15T08:19:04.766Z"
format: "markdown"
converted_by: "html-to-md-ai"
---
This guide covers installing and operating the MCP server on your DreamFactory host: the Node.js daemon, configuration, and running behind a reverse proxy.

- To create an MCP service in the admin interface, see [Creating an MCP Server Service](/AI/mcp-service-creation).

- For the available tools, request format, and required headers, see [MCP Server](/AI/mcp-server).

## Overview[​](#overview)

The MCP server runs in two parts on your DreamFactory host:

- **PHP routes** served by DreamFactory at `/mcp/{service}`

- **A Node.js daemon** that handles the MCP protocol layer (the PHP side forwards to it)

Clients connect to `https://<host>/mcp/{service}` — never directly to the daemon.

## Prerequisites[​](#prerequisites)

A working DreamFactory installation, plus the following **on the server**:

- **Node.js 20+ and npm** — required to build and run the daemon. On Ubuntu:

```
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -sudo apt-get install -y nodejsnode -v && npm -v
```

- The `dreamfactory/df-mcp-server` package (included in the commercial composer set).

Confirm the package and routes are present:

```
ls vendor/dreamfactory/df-mcp-serverphp artisan route:list | grep '/mcp/'
```

## The MCP daemon[​](#the-mcp-daemon)

The daemon lives under `vendor/dreamfactory/df-mcp-server/daemon/` and listens on `127.0.0.1:8006`.

Build it:

```
cd vendor/dreamfactory/df-mcp-server/daemonnpm installnpm run build
```

Run it via the shipped start script:

```
vendor/dreamfactory/df-mcp-server/scripts/start-daemon.sh
```

### Daemonizing for production[​](#daemonizing-for-production)

No systemd unit ships by default. Add one so the daemon starts on boot and restarts on crash — `/etc/systemd/system/df-mcp-daemon.service`:

```
[Unit]Description=DreamFactory MCP DaemonAfter=network.target[Service]Type=simpleUser=www-dataExecStart=/opt/dreamfactory/vendor/dreamfactory/df-mcp-server/scripts/start-daemon.shRestart=alwaysRestartSec=5StandardOutput=journalStandardError=journalEnvironment=NODE_ENV=production[Install]WantedBy=multi-user.target
```

```
sudo systemctl daemon-reloadsudo systemctl enable --now df-mcp-daemonsudo systemctl status df-mcp-daemon
```

### Viewing the daemon logs[​](#viewing-the-daemon-logs)

Because the unit sends output to the journal (`StandardOutput=journal`), read the daemon's logs with `journalctl`:

```
sudo journalctl -u df-mcp-daemon -f             # follow livesudo journalctl -u df-mcp-daemon -n 100         # last 100 linessudo journalctl -u df-mcp-daemon --since "15 min ago"
```

tipPoint `ExecStart` at the shipped `start-daemon.sh`, which runs the compiled `dist/server.js`. Don't point it at `tsx` — that's a dev-only dependency and is removed by a production `npm install --omit=dev`.

noteIn the Docker/compose stack, the daemon starts automatically when `ENABLE_MCP_DAEMON` is set, so no systemd unit is needed.

## Configuration[​](#configuration)

Set these in your DreamFactory `.env`:

VariableSet toNotes`APP_URL``https://df.example.com`The external URL clients use. Not `http://localhost`.`DF_FRONTEND_URL`*(optional)*Override only if the admin SPA is on a different host.`LOG_LEVEL``warning`Set to `debug` while troubleshooting OAuth, then revert.

warning`APP_URL` must be the **external HTTPS URL** clients reach. It drives the OAuth discovery and callback URLs as well as server-side session validation. If it is left as `http://localhost` (or any value clients can't reach), MCP authentication fails — typically as a login page that loops.

Verify the value actually in use:

```
php artisan tinker --execute="echo config('app.url');"
```

## Applying configuration changes[​](#applying-configuration-changes)

DreamFactory caches configuration, so editing `.env` alone is not enough:

```
php artisan config:clear            # required — .env is ignored while config is cachedphp artisan config:cache            # only if you run cached config in productionsudo systemctl restart php8.5-fpm   # use your PHP version; also clears OPcache
```

- **Don't** run `php artisan` as `root` — it creates root-owned cache files that PHP-FPM can't read, causing 500s. Run it as a normal user. If it already happened, reset ownership to your web-server user, e.g. `sudo chown -R www-data:www-data storage bootstrap/cache`.

- **Don't** rely on a PHP-FPM restart alone to apply `.env` changes — run `config:clear` first.

- Restart PHP-FPM after editing any vendor PHP file (OPcache won't pick it up otherwise).

## Running behind a reverse proxy[​](#running-behind-a-reverse-proxy)

When a proxy (AWS ALB, nginx, Cloudflare) terminates TLS and forwards traffic to DreamFactory over HTTP, MCP OAuth and the rest of DreamFactory take different paths.

MCP OAuth discovery reads `APP_URL` directly. Set it to the external HTTPS URL (see [Configuration](#configuration)). That is what makes discovery advertise `https://` URLs.

caution`APP_URL` is sufficient for MCP OAuth discovery. It is **not** sufficient for redirects. Laravel builds the root-route redirect (`/` → the admin UI) from the incoming request, so behind a TLS-terminating proxy the `Location` header comes back as `http://` unless you set `FORCE_HTTPS` or pass `HTTPS` through to PHP. See [Redirects still going to HTTP](#redirects-still-going-to-http) and [Web Server — TLS](/getting-started/optimizing-dreamfactory/web-server#tls).

After applying `APP_URL`, confirm OAuth discovery advertises **https** URLs:

```
curl -s https://df.example.com/.well-known/oauth-authorization-server/mcp/<service> \  | jq -r '.issuer, .authorization_endpoint'
```

If those URLs come back as `https://…`, no further proxy configuration is needed for MCP OAuth.

### If discovery still shows `http://`[​](#if-discovery-still-shows-http)

This means the proxy's scheme isn't reaching PHP. Pass it through in your nginx site:

```
# in the http { } blockmap $http_x_forwarded_proto $fcgi_https { default off; https on; }# in server { } -> location ~ \.php$ { }fastcgi_param HTTPS $fcgi_https;
```

```
sudo nginx -t && sudo systemctl reload nginx
```

### Redirects still going to HTTP[​](#redirects-still-going-to-http)

Laravel builds redirects from the incoming request. Behind a TLS-terminating proxy the app only sees the HTTP leg, so `Location` headers come back as `http://…` even when `APP_URL` is correct. The supported application-level switch is:

```
# in .envFORCE_HTTPS=true
```

Then run `php artisan config:clear` and restart PHP-FPM. Do **not** follow that with `config:cache` — `FORCE_HTTPS` is read from the environment at boot, not from cached config, so caching config makes the setting a no-op and redirects go back to `http://`.

The nginx `HTTPS` FastCGI parameter above is the other supported path: it tells PHP the original request was HTTPS. Use both when TLS terminates in front of DreamFactory.

DreamFactory does not ship `app/Http/Middleware/TrustProxies.php`. Do not add one.

Full reverse-proxy notes are on [Web Server — TLS](/getting-started/optimizing-dreamfactory/web-server#tls).

## Connecting a client[​](#connecting-a-client)

After [creating an MCP service](/AI/mcp-service-creation), point your client at `https://<host>/mcp/{service}`:

- **No trailing slash** — a trailing slash breaks OAuth discovery.

- **Not** `/api/v2/...` — that is the REST API and returns `400 No session token or API Key`.

Example VS Code `mcp.json`:

```
{  "servers": {    "df-poc": { "type": "http", "url": "https://df.example.com/mcp/poc" }  }}
```

noteNative clients (VS Code, Cursor, Claude Desktop) receive the OAuth code on a rotating `http://127.0.0.1:<port>/` listener. If you see `redirect_uri is not registered for this client`, free up the client's preferred fixed port (VS Code uses `33418`) and reconnect.

For tool calls and the full list of required headers, see [MCP Server](/AI/mcp-server#required-headers).

## Troubleshooting[​](#troubleshooting)

Enable debug logging first — OAuth failures are otherwise silent. Set `LOG_LEVEL=debug`, run `config:clear`, then:

```
tail -f storage/logs/dreamfactory.logsudo tail -f /var/log/nginx/access.log | grep -E "oauth-callback|user/session"
```

SymptomFixLogin page loops or flickersSet `APP_URL` to the external HTTPS URL, `config:clear`, restart PHP-FPMDiscovery advertises `http://` URLsSet `APP_URL`; if it still shows `http://`, add the nginx `HTTPS` FastCGI paramRoot URL (or other redirects) go to `http://`Set `FORCE_HTTPS=true`, `config:clear` (not `config:cache`), restart PHP-FPM. See [Web Server — TLS](/getting-started/optimizing-dreamfactory/web-server#tls)`400 No session token or API Key`Client URL points at `/api/v2/...` — use `/mcp/{service}``.well-known` returns 404Use the service-scoped path `/.well-known/oauth-authorization-server/mcp/{service}``redirect_uri is not registered`Rotating loopback port — free/reuse the client's fixed port and reconnect404 on all `/mcp/*`Confirm the package is installed, then `php artisan route:clear`Config change has no effectRun `config:clear` (not just a PHP-FPM restart); ensure cache files aren't root-owned

## See also[​](#see-also)

- [MCP Server](/AI/mcp-server) — protocol overview, tools, and request format

- [Creating an MCP Server Service](/AI/mcp-service-creation)

- [Custom Login Page for MCP](/AI/mcp-custom-login-page)

- [Web Server — TLS](/getting-started/optimizing-dreamfactory/web-server#tls) — reverse-proxy scheme, `FORCE_HTTPS`, and redirects