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_TOKENis 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_TOKENare 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_fileis.env.local, but the sharedMERCURE_JWT_KEY/MERCURE_SERVER_URLlive 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-uploadsdirectory. Thenode:22-slimbase image ships with anodeuser at uid 1000, and the Dockerfile setsUSER node.
Local agent-server development (hot reload on
.tsfile changes) is an opt-in override. See Development for thedocker-compose.override.yamlsnippet that mounts the source and runstsx 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-uploadsIf 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--buildflag forces a rebuild even if the image already exists. On subsequent starts without code changes, plaindocker compose up -d agent-serverreuses 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
- Open
https://<YOUR_PIMCORE_DOMAIN>/pimcore-studioand log in. - Click the agent chat sidebar button.
- Send a test message - you should see a streaming reply.
- 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.yamland the build artifacts are committed underpublic/build/. There is no need to runnpm run buildlocally 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.