A Model Context Protocol (MCP) server that exposes the Alfresco Content Services REST API to MCP-aware clients such as Claude Desktop, the Gemini CLI, and other AI assistants.
It lets users and administrators of an on-prem Alfresco server query and manage their repository through natural-language conversations with an LLM.
-
Up to 135 tools covering the full Alfresco Core API surface — nodes, search, sites, people, groups, tags, categories, comments, ratings, renditions, versions, trashcan, shared links, favorites, activities, downloads, actions, audit, auth tickets, probes, storage management, and the content model API. A further 13 rules tools are registered automatically when the Rules REST API module is detected on the connected ACS instance.
-
Per-session capability-based tool filtering — in session mode, each MCP connection starts with only 6 session-management tools. After the user authenticates, the server auto-detects their Alfresco role and adds the appropriate profile subset. The MCP client receives
notifications/tools/list_changedautomatically and fetches the updated list without reconnecting. -
Six role profiles (flat inclusive lists, no inheritance):
Profile Tools Description minimal6 Session management only — shown before authentication reader~55 Read-only content, sites, people, groups, search collaborator~70 Reader + comments, ratings, tags, favorites editor~91 Collaborator + create/edit/delete, versions, downloads coordinator~109 Editor + site management, categories, storage admin~122+ Full access including user/group management and audit -
Streamable HTTP MCP transport on a single
/mcpendpoint. -
Multiple auth modes — choose the strategy that fits your deployment (see Authentication below).
-
Session mode — each MCP session authenticates independently via Keycloak device authorization grant; no credentials are stored in the server config and no passwords pass through the LLM. Role is auto-detected from Alfresco group memberships (
ALFRESCO_ADMINISTRATORS→ admin, otherwisedefaultProfilefrom config oreditor). -
Auto-detects ACS version at startup via the Discovery API and adjusts the registered tool set accordingly.
-
/healthzendpoint suitable for Kubernetes / load-balancer health checks. -
CORS enabled for all origins so browser-based MCP clients can connect.
-
Single statically linked Go binary — no Python venv, no Node.js, no JVM.
- Alfresco Content Services 7.x or later reachable over HTTP(S).
- Go 1.25 or later (for building from source).
- go-task (
task) for the build shortcuts. - podman or Docker for container image builds.
task build # produces bin/alfresco-mcp
task image # builds alfresco-mcp:latest container imageOther useful targets:
| Task | Purpose |
|---|---|
task test |
Run all unit tests with the race detector |
task test-cover |
Run tests and print a function-level coverage report |
task lint |
Run golangci-lint |
task fmt |
Run go fmt ./... |
task tidy |
Run go mod tidy |
task clean |
Remove bin/ and coverage.out |
The server is configured via a YAML config file or environment variables.
Copy config-example.yaml to config.yaml (or any config-*.yaml name —
these are gitignored so credentials stay out of version control) and
customize it:
cp config-example.yaml config.yaml
$EDITOR config.yaml
./bin/alfresco-mcp --config config.yamlSee config-example.yaml for annotated examples of every auth mode.
Session mode is the recommended setup for externally hosted servers. Each MCP session authenticates independently via Keycloak Device Authorization Grant (RFC 8628). No credentials live in the config file.
mode: session
allowArbitraryServers: false # true allows the LLM to pass any https:// URL
default: myserver
servers:
myserver:
url: https://acs.example.com
auth:
mode: device-code
tokenUrl: https://acs.example.com/auth/realms/alfresco/protocol/openid-connect/token
clientId: alfrescoStart the server:
./bin/alfresco-mcp --config config.yaml --no-version-detectThe LLM then drives the login flow:
- Call
session_login(no arguments if a default server is configured) → getuser_code+verification_uri. - Show the user the URL and code; they authenticate in a browser.
- Poll
session_auth_statusuntil it returns"authenticated". The server detects the user's Alfresco role and adds the appropriate profile tools automatically — the MCP client receivesnotifications/tools/list_changed. - All subsequent Alfresco tools use the session token automatically. If the access token expires mid-session the server silently refreshes it using the stored OAuth2 refresh token — no re-login required unless the refresh token is also revoked.
- Optionally call
session_switch_profileto upgrade to a larger profile (e.g.admin), orsession_list_profilesto see what's available. - Call
session_logoutto clear the token. Sessions idle for more than 2 hours are swept automatically and their server-side state is freed.
To control which profile non-admin users receive, add defaultProfile to the
server config:
servers:
myserver:
url: https://acs.example.com
auth:
mode: device-code
tokenUrl: ...
clientId: alfresco
defaultProfile: reader # reader | collaborator | editor | coordinator | adminFor servers where credentials are fixed at deploy time:
default: myserver
servers:
myserver:
url: https://acs.example.com
auth:
mode: basic # or oauth2-password / oauth2-client / passthrough
username: admin
password: secret./bin/alfresco-mcp --config config.yaml
./bin/alfresco-mcp --config config.yaml --server myserver| Variable | Required | Default | Description |
|---|---|---|---|
ALFRESCO_URL |
yes* | — | Base URL of the Alfresco server |
ALFRESCO_USERNAME |
no | — | Username for server-side Basic auth |
ALFRESCO_PASSWORD |
no | — | Password for server-side Basic auth |
ALFRESCO_CONFIG |
no | — | Path to the YAML config file |
ALFRESCO_SERVER |
no | — | Named profile to use from the config file |
ADDR |
no | :8080 |
TCP address the HTTP server listens on |
* Required when --config / ALFRESCO_CONFIG is not set.
ALFRESCO_URL=https://acs.example.com \
ALFRESCO_USERNAME=admin \
ALFRESCO_PASSWORD=admin \
./bin/alfresco-mcp| Flag | Default | Description |
|---|---|---|
--config |
— | Path to the config YAML file |
--server |
— | Named server profile in the config |
--addr |
:8080 |
Listen address |
--no-version-detect |
false | Skip ACS capability detection at startup |
--log-level |
info |
Log verbosity: debug, info, warn, error |
--max-sessions |
0 |
Maximum concurrent MCP sessions (0 = unlimited) |
--session-rate |
0 |
Max new sessions per second (0 = unlimited); burst is 2× the rate |
| Mode | Description |
|---|---|
passthrough |
Validates the incoming JWT Bearer token (requires resourceMetadata), then forwards it to ACS. |
passthrough-unsafe |
Forwards the Authorization header verbatim without validation. Use only in trusted environments. |
basic |
Static username + password configured on the server. |
oauth2-password |
OAuth2 Resource Owner Password Credentials (ROPC) flow against Keycloak. Token is cached and auto-refreshed. |
oauth2-client |
OAuth2 Client Credentials flow (service account). Token is cached and auto-refreshed. No user involved. |
device-code |
OAuth2 Device Authorization Grant (RFC 8628) — used as a server-level auth for single-user setups. Prompts on stderr at startup. |
session (top-level mode) |
Per-MCP-session device code or Bearer token auth. The LLM drives the login flow; no credentials in the config. Recommended for externally hosted servers. |
Note on service-account modes (
basic,oauth2-*,device-code): the server acts as a single authenticated identity, so access is gated by that account's ACS permissions. These modes are incompatible withresourceMetadata(which implies per-user identity).
When resourceMetadata is configured, the server acts as an OAuth 2.0
Protected Resource. MCP clients that implement the MCP OAuth spec
(Copilot Studio, OpenAI ChatGPT, etc.) can authenticate transparently
without going through the device-code flow. See
docs/oauth-protected-resource.md for the
full design.
Three auth paths are supported simultaneously:
| Request | Behavior |
|---|---|
No Authorization header |
Allowed through; device-code tools handle auth |
| Valid JWT Bearer token | Validated against Keycloak JWKS; claims injected into session |
| Invalid / expired Bearer | 401 with WWW-Authenticate: Bearer error="invalid_token" |
Keycloak configuration required: add an Audience mapper to the alfresco
client in Keycloak → Client Scopes → alfresco scope → Mappers → Add mapper →
"Audience" — set Included Audience to alfresco. This ensures the aud claim
contains alfresco, which alfresco-mcp validates.
Config example:
mode: session
resourceMetadata:
enabled: true
resourceUrl: https://mcp.example.com/mcp # public URL of the /mcp endpoint
authorizationServer: https://acs.example.com/auth/realms/alfresco
expectedAudiences:
- alfresco
servers:
myserver:
url: https://acs.example.com
auth:
mode: device-code
tokenUrl: https://acs.example.com/auth/realms/alfresco/protocol/openid-connect/token
clientId: alfrescoThe server publishes /.well-known/oauth-protected-resource automatically
(returns 404 when resourceMetadata is disabled).
On startup, the server probes the ACS Discovery API and the Rules REST API
endpoint to build a ServerCapabilities struct:
time=... level=INFO msg="capabilities detected" version="25.3.0" edition=Enterprise has_rules=false
version/edition— populated from the Discovery API. If the calling user lacks admin access to Discovery, these remain empty but the server still starts.has_rules—trueif the Rules REST API module is deployed. The 13 rules tools (nodes_list_rule_sets,nodes_create_rule, …) are only registered when this istrue.
Pass --no-version-detect to skip probing and register all tools
unconditionally (required in session mode since no credentials are available
at startup).
Point any MCP client at the server URL. The LLM will call session_login
to initiate the Keycloak device code flow:
{
"mcpServers": {
"alfresco": {
"type": "http",
"url": "https://acs.example.com/mcp"
}
}
}These clients implement the MCP OAuth spec and discover auth metadata
automatically from /.well-known/oauth-protected-resource. No manual
token configuration needed — just point them at the /mcp endpoint:
https://mcp.example.com/mcp
Requires resourceMetadata to be configured (see above). The client will
redirect users to Keycloak for login and pass the resulting JWT on each request.
Session mode (recommended — no credentials in config):
Add to ~/.config/claude/claude_desktop_config.json (Linux) or
~/Library/Application Support/Claude/claude_desktop_config.json (macOS):
{
"mcpServers": {
"alfresco": {
"type": "http",
"url": "https://mcp.example.com/mcp"
}
}
}The LLM will call session_login and guide you through the Keycloak device
code flow in the first conversation.
Static auth (local / single-user setups):
{
"mcpServers": {
"alfresco": {
"type": "http",
"url": "http://localhost:8080/mcp",
"headers": {
"Authorization": "Basic <base64(user:pass)>"
}
}
}
}Omit headers when the server uses server-side auth (basic, oauth2-*, device-code).
{
"mcpServers": {
"alfresco": {
"httpUrl": "http://localhost:8080/mcp",
"timeout": 30000
}
}
}-
TLS:
alfresco-mcpspeaks plain HTTP. Put it behind a reverse proxy (nginx, Traefik, HAProxy, an Ingress controller) that terminates TLS. -
CORS: wide-open (
*) by default. Tighten at the proxy layer if needed. -
Health check:
GET /healthzreturns200 ok. -
Metrics:
GET /metricsreturns Prometheus-compatible text exposition (no external dependencies — built-in zero-dep implementation). Exposed metrics:Metric Type Description alfresco_mcp_tool_calls_total{tool, result}counter Per-tool invocations, labelled ok/erroralfresco_mcp_tool_duration_mshistogram Tool handler duration (buckets: 1/5/25/100/500/2000 ms) alfresco_mcp_acs_calls_total{method, status}counter Outbound ACS REST calls by HTTP method and status class alfresco_mcp_acs_duration_mshistogram ACS call duration (buckets: 5/25/100/500/2000/10000 ms) alfresco_mcp_sessions_activegauge Current live MCP sessions alfresco_mcp_sessions_created_totalcounter Total sessions created since startup alfresco_mcp_sessions_evicted_totalcounter Sessions removed by the TTL sweeper alfresco_mcp_rate_limited_totalcounter Session-creation requests rejected by the rate limiter -
Request ID: every request is assigned an
X-Request-Idheader (UUID v4). If the client sends one it is echoed back; otherwise one is generated. The ID appears in alltool_call/tool_errorlog lines asrequest_id=for end-to-end correlation across proxy logs and tool logs. -
Graceful shutdown: on
SIGINT/SIGTERMthe server stops accepting new connections and waits up to 30 seconds for in-flight SSE streams to drain before exiting. -
Session DoS protection: use
--max-sessionsto cap total concurrent sessions (returns503when full) and--session-rateto rate-limit new session creation (returns429when exceeded; burst is 2× the rate). -
Logging: structured
slogkey=value format to stderr. Use--log-level(orLOG_LEVELenv) to control verbosity:Level What is logged errorFatal startup and HTTP server errors only warn+ tool calls that return errors ( tool_errorwitherror=)info+ server startup, session lifecycle, every tool call ( tool_call), auth events (auth_login,auth_authenticated,auth_profile_set,auth_logout)debug+ every outbound ACS HTTP request ( acs_callwithmethod,path,status,ms)Every
tool_call/tool_errorline includestool=,session=,user=(Alfresco username, set after auth),request_id=, andms=(duration). This makes it straightforward to answer "which tools does each user actually invoke?" by grepping the log — useful input for tuning role profiles. -
Nginx example (for session mode on port 8091):
location /mcp { proxy_pass http://localhost:8091; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_buffering off; proxy_read_timeout 300; }
-
systemd example:
[Service] ExecStart=/opt/alfresco-mcp/alfresco-mcp --config /opt/alfresco-mcp/config.yaml --addr :8091 Restart=on-failure
-
Container:
podman run --rm -p 8080:8080 \ -v /path/to/config.yaml:/etc/alfresco-mcp/config.yaml:ro \ alfresco-mcp:latest \ --config /etc/alfresco-mcp/config.yaml
Licensed under the Apache License, Version 2.0.
Copyright 2026 Redpill Linpro AB.