Skip to content

Repository files navigation

alfresco-mcp

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.

Features

  • 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_changed automatically and fetches the updated list without reconnecting.

  • Six role profiles (flat inclusive lists, no inheritance):

    Profile Tools Description
    minimal 6 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 /mcp endpoint.

  • 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, otherwise defaultProfile from config or editor).

  • Auto-detects ACS version at startup via the Discovery API and adjusts the registered tool set accordingly.

  • /healthz endpoint 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.

Requirements

  • 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.

Building

task build          # produces bin/alfresco-mcp
task image          # builds alfresco-mcp:latest container image

Other 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

Running

The server is configured via a YAML config file or environment variables.

Config file (recommended)

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.yaml

See config-example.yaml for annotated examples of every auth mode.

Session 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: alfresco

Start the server:

./bin/alfresco-mcp --config config.yaml --no-version-detect

The LLM then drives the login flow:

  1. Call session_login (no arguments if a default server is configured) → get user_code + verification_uri.
  2. Show the user the URL and code; they authenticate in a browser.
  3. Poll session_auth_status until it returns "authenticated". The server detects the user's Alfresco role and adds the appropriate profile tools automatically — the MCP client receives notifications/tools/list_changed.
  4. 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.
  5. Optionally call session_switch_profile to upgrade to a larger profile (e.g. admin), or session_list_profiles to see what's available.
  6. Call session_logout to 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 | admin

Static auth modes

For 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

Environment variables (legacy / simple setups)

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

CLI flags

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

Authentication

Auth modes

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 with resourceMetadata (which implies per-user identity).

OAuth 2.0 Protected Resource (RFC 9728)

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: alfresco

The server publishes /.well-known/oauth-protected-resource automatically (returns 404 when resourceMetadata is disabled).

Capability detection

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_rulestrue if the Rules REST API module is deployed. The 13 rules tools (nodes_list_rule_sets, nodes_create_rule, …) are only registered when this is true.

Pass --no-version-detect to skip probing and register all tools unconditionally (required in session mode since no credentials are available at startup).

Configuring MCP clients

Session mode (no Authorization header needed)

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"
    }
  }
}

Copilot Studio / ChatGPT (OAuth 2.0 Protected Resource)

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.

Claude Desktop (session mode or static auth)

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).

Gemini CLI

{
  "mcpServers": {
    "alfresco": {
      "httpUrl": "http://localhost:8080/mcp",
      "timeout": 30000
    }
  }
}

Production deployment

  • TLS: alfresco-mcp speaks 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 /healthz returns 200 ok.

  • Metrics: GET /metrics returns 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/error
    alfresco_mcp_tool_duration_ms histogram 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_ms histogram ACS call duration (buckets: 5/25/100/500/2000/10000 ms)
    alfresco_mcp_sessions_active gauge Current live MCP sessions
    alfresco_mcp_sessions_created_total counter Total sessions created since startup
    alfresco_mcp_sessions_evicted_total counter Sessions removed by the TTL sweeper
    alfresco_mcp_rate_limited_total counter Session-creation requests rejected by the rate limiter
  • Request ID: every request is assigned an X-Request-Id header (UUID v4). If the client sends one it is echoed back; otherwise one is generated. The ID appears in all tool_call / tool_error log lines as request_id= for end-to-end correlation across proxy logs and tool logs.

  • Graceful shutdown: on SIGINT/SIGTERM the 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-sessions to cap total concurrent sessions (returns 503 when full) and --session-rate to rate-limit new session creation (returns 429 when exceeded; burst is 2× the rate).

  • Logging: structured slog key=value format to stderr. Use --log-level (or LOG_LEVEL env) to control verbosity:

    Level What is logged
    error Fatal startup and HTTP server errors only
    warn + tool calls that return errors (tool_error with error=)
    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_call with method, path, status, ms)

    Every tool_call / tool_error line includes tool=, session=, user= (Alfresco username, set after auth), request_id=, and ms= (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

License

Licensed under the Apache License, Version 2.0.

Copyright 2026 Redpill Linpro AB.

About

MCP server for Alfresco Content Services, written in Go. 135+ tools across the Core API — nodes, search, sites, people, groups, versions, permissions. Streamable HTTP transport, per-session Keycloak device-code login with role-based tool filtering, and OAuth 2.0 Protected Resource (RFC 9728). Single static binary.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages