Skip to main content
Version: 2026.1

Installation

Prerequisites

  • Docker and Docker Compose
  • Pimcore 2026.x

1. Add the Bundle (Composer)

composer require pimcore/pimcore-agent-bundle:dev-main

2. Register the PHP Bundle

In config/bundles.php:

Pimcore\Bundle\PimcoreAgentBundle\PimcoreAgentBundle::class => ['all' => true],

3. Configure Security

Three additions to config/packages/security.yaml are required.

3.1 Bundle-API firewall

The agent's bundle API routes (/pimcore-studio/api/bundle/agent/*) are protected by a dedicated firewall that authenticates via the chat-scoped MCP access token. Wire it before the pimcore_studio firewall (more specific pattern must come first):

security:
firewalls:
# ... existing firewalls ...
# Place before pimcore_studio.
pimcore_agent_bundle_api: '%pimcore_agent.bundle_api_firewall_settings%'
pimcore_studio: '%pimcore_studio_backend.firewall_settings%'

The parameter is exposed by PimcoreAgentExtension and contains the full firewall definition (stateless, authenticator chain: SessionBridge → McpAccessTokenAuthenticator → PAT).

3.2 MCP firewall

The agent's MCP tool servers are exposed under /pimcore-mcp/ and must be protected by the pimcore_mcp firewall shipped by the Studio Backend Bundle. Without it those routes are not properly authenticated. Wire the firewall and add its access-control rule:

security:
firewalls:
# ... existing firewalls ...
# Place before any catch-all firewall.
pimcore_mcp: '%pimcore_studio_backend.mcp_firewall_settings%'

access_control:
# ... existing rules ...
- { path: ^/pimcore-mcp/, roles: ROLE_PIMCORE_USER }

The parameter contains the full firewall definition (stateless, authenticator chain, user provider). See Studio Backend → MCP Server Infrastructure.

3.3 Config-export access rule

Add this access_control rule to allow the agent-server to call the config export endpoint without a Pimcore user session. Place it before the general pimcore-studio/api catch-all rule:

security:
access_control:
# ... existing rules ...
- { path: ^/pimcore-studio/api/bundle/agent/configurations/export$, roles: PUBLIC_ACCESS }
- { path: ^/pimcore-studio/api, roles: ROLE_PIMCORE_USER }

4. Configure Environment Variables (.env.local)

The agent-server reads its configuration from .env.local (loaded into the container via the env_file directive). All agent-server-specific variables are prefixed with AGENT_SERVER_. The PORT, HOST, NODE_ENV, and PIMCORE_INTERNAL_URL are kept as standard Node/Pimcore conventions.

# --- Connection from PHP to the agent-server ---
AGENT_SERVER_URL=http://agent-server:3032
AGENT_SERVER_ADMIN_TOKEN=your-secret-token

# --- Adapter ---
AGENT_SERVER_ADAPTER=copilot # copilot (production) | dummy (local dev, no LLM call)
AGENT_SERVER_DUMMY_MODE=scripted # echo | scripted | random (dummy adapter only)

# --- Provider token secrets (referenced via ${VAR} in pimcore_agent.inference.providers) ---
ANTHROPIC_API_KEY=sk-ant-...
# GH_COPILOT_TOKEN=ghp_... # for github auth_mode providers

# --- Optional ---
AGENT_SERVER_LOG_LEVEL=info # verbose | debug | info | warn | error

LLM provider configuration (auth mode, endpoint, model list, per-model limits) lives in the Symfony config tree, not in env vars. Define providers in config/packages/pimcore_agent.yaml and reference secret values with ${VAR_NAME} placeholders — the agent-server resolves them from the container environment at session time. See Configuration → Inference Providers for the full schema and setup examples for Anthropic, OpenAI, GitHub Copilot, Ollama, and HuggingFace endpoints.

AGENT_SERVER_ADMIN_TOKEN is shared between the PHP side and the agent-server container. PHP uses it to authenticate reload and proxy requests; the agent-server uses it to protect its /agent-server/api/admin/* endpoints.

MCP server credentials like MCP_PIMCORE_URL / MCP_PIMCORE_TOKEN are interpolated from agent YAML configs. They are user-defined external service credentials, not part of the agent-server's strict config schema, so they are not prefixed. See Configuration → MCP Servers.

See Configuration → Environment Variables for the full env-var reference.

5. Install the Pimcore Bundle

docker compose exec php bin/console pimcore:bundle:install PimcoreAgentBundle
docker compose exec php bin/console cache:clear

This creates the bundle_agent_sessions, bundle_agent_messages, and bundle_agent_proposal_statuses database tables for persistent session and proposal storage, and adds the pimcore_agent_config user permission. The installer only touches the database and Pimcore settings store — it does not talk to the agent-server container, so the agent-server does not need to be running at this point.

Grant the pimcore_agent_config permission

Admin users have all permissions by default. For non-admin users to access the agent and skill configuration editors, grant them the pimcore_agent_config permission in System → Users & Roles.

Choose where Studio UI edits are stored (optional)

By default, the agent and skill configurations created through the Studio UI are persisted in Pimcore's settings_store database table — no file system access needed. You can switch to file-based storage (YAML files under var/config/agent/) if you want to version-control administrator edits in Git, or disable Studio UI editing entirely on environments where it should not be available.

Add to config/packages/pimcore_agent.yaml:

pimcore_agent:
config_location:
agent_configurations:
write_target:
type: settings-store # default — DB-backed
# type: symfony-config # file-based — writes var/config/agent/agents/*.yaml
# type: disabled # disables Studio UI editing of agents
skill_configurations:
write_target:
type: settings-store
# type: symfony-config # writes var/config/agent/skills/*.yaml
# type: disabled

When using symfony-config, a Symfony cache clear is needed after external file changes — the bundle handles this automatically for changes made through the UI.

6. Add the Agent-Server Service to docker-compose.yaml

The agent-server ships with a multi-stage Dockerfile that compiles TypeScript to dist/, installs production-only dependencies, and runs node dist/server.js as CMD. The default docker-compose setup uses build: for this production image — no source bind mount, no tsx watch, no dev dependencies in the runtime container.

agent-server:
user: '1000:1000'
build:
context: ./dev/pimcore/pimcore-agent-bundle/agent-server
ports:
- "3032:3032"
volumes:
# Runtime state only — source and node_modules are baked into the image.
- ./var/tmp/copilot-state:/app/.copilot-state
- ./var/tmp/agent-uploads:/app/uploads
env_file:
- path: .env.local
required: false
environment:
NODE_ENV: production
PORT: 3032
PIMCORE_INTERNAL_URL: http://nginx
# Forward the EXISTING shared Mercure secret + hub URL (from the root .env,
# the same values the rest of the stack uses) so live cross-client chat sync
# can publish. These are not new variables — they are not created for the
# bundle. Omit them and chat still works, but without live sync.
MERCURE_JWT_KEY: ${MERCURE_JWT_KEY}
MERCURE_SERVER_URL: ${MERCURE_SERVER_URL:-http://mercure/.well-known/mercure}
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
- nginx

Mercure forwarding. The agent-server's env_file is .env.local, but the shared MERCURE_JWT_KEY / MERCURE_SERVER_URL live in the root .env, so they must be passed through explicitly as above (compose substitutes ${…} from the root .env). Without them the publisher starts in no-op mode (a startup warning is logged) — chat works, but live cross-tab/device sync is off. See Real-time Sync.

user: '1000:1000' must match the PHP container's user so both services can read/write the shared ./var/tmp/agent-uploads directory. The node:22-slim base image ships with a node user at uid 1000, and the Dockerfile sets USER node.

Local agent-server development (hot reload on .ts file changes) is an opt-in override. See Development for the docker-compose.override.yaml snippet that mounts the source and runs tsx watch.

7. Configure the Nginx Proxy

Add to .docker/nginx.conf (or your equivalent), after any existing proxy blocks:

# Agent-server backend proxy (^~ prevents regex location override for static assets)
location ^~ /agent-server/api/ {
proxy_pass http://agent-server:3032/agent-server/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Connection '';
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 300s;
chunked_transfer_encoding off;
}

proxy_buffering off and chunked_transfer_encoding off are critical for SSE streaming.

8. Build and Start the Agent-Server Container

Before starting: create the bind-mount directories as your user so that Docker does not create them as root:

mkdir -p var/tmp/copilot-state var/tmp/agent-uploads

If you configured different host paths in docker-compose.yaml, create those instead.

Build the agent-server image (runs the multi-stage Dockerfile: npm ci, tsc, prod-only npm ci --omit=dev) and start it:

docker compose up -d --build agent-server

Note: rebuild is required whenever any file under dev/pimcore/pimcore-agent-bundle/agent-server/ changes (TypeScript source, agent YAML configs, package.json, Dockerfile). The --build flag forces a rebuild even if the image already exists. On subsequent starts without code changes, plain docker compose up -d agent-server reuses the cached image.

Verify it started cleanly:

docker compose logs agent-server | tail -20
curl https://<YOUR_PIMCORE_DOMAIN>/agent-server/api/health

The health endpoint should return {"status":"ok","adapter":"...","sessionStore":"pimcore","uptime":...}.

9. Verify

  1. Open https://<YOUR_PIMCORE_DOMAIN>/pimcore-studio and log in.
  2. Click the agent chat sidebar button.
  3. Send a test message - you should see a streaming reply.
  4. Open Chrome DevTools → Network tab and confirm the requests go to /agent-server/api/chat.

Note: the studio frontend (React Module Federation plugin) is built by the GitHub Actions workflow at .github/workflows/studio-frontend-build.yaml and the build artifacts are committed under public/build/. There is no need to run npm run build locally for installation.

Logging

The MCP access token (Authorization: Bearer pmcp_…) is a credential. If you enable verbose request logging in monolog (e.g. a RequestProcessor with header capture, or a custom processor that records $request->headers), redact the Authorization header to avoid leaking tokens into log streams. The agent-server's Fastify logger already redacts Authorization and Cookie via Pino's redact option — those values appear as [REDACTED] in all structured log output.