Skip to main content
Glama
Jahia

Jahia MCP Community Server

Official
by Jahia
README.md
# Jahia MCP Community Server

**GitHub:** https://github.com/Jahia/jahia-mcp-community-server

An OSGi bundle that exposes Jahia's GraphQL API as a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server, enabling AI assistants such as Claude to query and mutate Jahia content directly over a secure HTTP endpoint.

## How it works

The servlet registers at `/modules/community-mcp` and implements the stateless MCP JSON-RPC 2.0 protocol over HTTP. When an MCP client calls a tool, the servlet dispatches the GraphQL request **in-process** via an OSGi service reference to `OsgiGraphQLHttpServlet` — no extra HTTP hop is involved.

```
MCP Client (Claude Code)
    │  POST /modules/community-mcp
    │  Authorization: APIToken <token>
    ▼
McpServlet  ──[whitelist check]──►  OsgiGraphQLHttpServlet
                                     (graphql-dxm-provider)
```

The caller's `Authorization` header and the current Jahia user are forwarded so that all JCR permission checks apply normally.

## Requirements

- Jahia 8.2+ with `graphql-dxm-provider` deployed
- `personal-api-tokens` module deployed (for API token authentication)
- Java 17

## Installation

Deploy the bundle JAR via the Jahia Module Manager or drop it into `$JAHIA_HOME/modules/`.

On first activation the module automatically seeds a set of default skills into JCR under `/sites/systemsite/contents/mcp-skills/`.

## Authentication

Access to `/modules/community-mcp` requires a personal API token with both the **`graphql`** and **`community-mcp`** scopes.

Generate one in Jahia under **Administration → Profile → Personal API Tokens**, or via GraphQL:

```graphql
mutation {
    admin {
        personalApiTokens {
            createToken(name: "my-mcp-token", scopes: ["graphql", "community-mcp"])
        }
    }
}
```

Pass the token in every request:

```
Authorization: APIToken <your-token>
```

The `community-mcp` permission is automatically granted to users with the `admin` role (configured in `META-INF/configurations/org.jahia.bundles.api.authorization-community-mcp.yml`).

## MCP client setup (Claude Code)

Add the following to `~/.claude/settings.json`:

```json
{
  "mcpServers": {
    "jahia-mcp": {
      "type": "http",
      "url": "http://localhost:8080/modules/community-mcp",
      "headers": {
        "authorization": "APIToken <your-token>"
      }
    }
  }
}
```

## Available tools

### `introspectSchema`

Returns all available top-level GraphQL query and mutation operations with full type details. Call this first to discover what operations and arguments are available before calling `executeGraphQL`.

No input arguments required.

### `executeGraphQL`

Executes any GraphQL query or mutation against Jahia's `graphql-dxm-provider`, subject to the configured whitelist.

| Field | Type | Required | Description |
|---|---|---|---|
| `query` | string | yes | GraphQL query or mutation |
| `variables` | object | no | Variables map for the operation |

**Example — list root child nodes:**

```graphql
query {
    jcr {
        nodeByPath(path: "/") {
            children {
                nodes {
                    name
                    path
                    primaryNodeType { name }
                }
            }
        }
    }
}
```

Introspection fields (`__schema`, `__type`) always pass regardless of whitelist configuration.

### `listSkills`

Returns the name, display name (`mcpName`), and description of every skill stored in JCR under `/sites/systemsite/contents/mcp-skills/`. Skills can be organized in sub-folders at any depth.

No input arguments required.

**Example response:**

```json
[
  {"name":"hello-jahia","mcpName":"Hello Jahia","description":"How to respond to Hello Jahia"}
]
```

### `getSkill`

Returns the full Markdown content of a skill by name. Call `listSkills` first to discover available names.

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | Skill name as returned by `listSkills` (may include sub-folder path, e.g. `default/hello-jahia`) |

## Skills

Skills are Markdown documents stored as `mcp:skill` JCR nodes. They allow Jahia-specific knowledge and instructions to be delivered dynamically to any MCP client session.

### Default skills

A set of default skills is seeded into JCR on module activation. They are defined as `.md` files under `src/main/resources/skills/` and are only created if the node does not already exist.

### Managing skills via GraphQL

All MCP operations are grouped under a single `mcp` namespace on `Query` and `Mutation`.

```graphql
# List all skills
query {
    mcp {
        skills {
            name
            mcpName
            description
            content
        }
    }
}

# Create or update a skill
mutation {
    mcp {
        saveSkill(
            name: "my-skill",
            mcpName: "My Skill Display Name",
            description: "What this skill does",
            content: "# My Skill\n\nInstructions for Claude..."
        )
    }
}

# Delete a skill
mutation {
    mcp {
        deleteSkill(name: "my-skill")
    }
}
```

### Adding a bundled default skill

Drop a `.md` file with frontmatter into `src/main/resources/skills/<subfolder>/`:

```markdown
---
mcpName: My Skill Display Name
description: Short description of what this skill does
---
Full Markdown instructions here...
```

The file name (without `.md`) becomes the JCR node name. The folder hierarchy mirrors the JCR sub-folder structure under `mcp-skills/`.


## Security notes

### Allow-all mode (empty whitelist)

When the whitelist is empty (the default), **all GraphQL operations are permitted** to any caller
who holds a valid API token with the `community-mcp` scope. The server logs a `WARN` on every
request in this state to make it visible in logs:

```
WARN McpServlet - MCP GraphQL gate is running in ALLOW-ALL mode (whitelist is empty). ...
```

Configure an explicit whitelist in **Administration → MCP Server** (or in
`META-INF/configurations/org.jahia.community.mcp.cfg`) to restrict which operations MCP clients
may call.

**Hardening recommendation:** treat the empty (allow-all) whitelist as an out-of-box convenience,
not a production posture. Because the `community-mcp` scope is granted to the `admin` role, a token
in allow-all mode can run any GraphQL operation the admin user is permitted to (bounded only by JCR
ACLs) — including administrative mutations. For least-privilege, always configure an explicit
whitelist scoped to exactly the operations your MCP clients need.

### Named fragment spreads blocked when whitelist is active

When a whitelist is configured, GraphQL queries that use **named fragment spreads**
(`...FragmentName`) are rejected. The whitelist gate cannot resolve spread contents without the
full fragment definition, so it fails closed rather than allowing a bypass.

Inline fragments (`... on TypeName { ... }`) are fully supported.

### X-Forwarded-For not trusted

Client IP logged in audit/block messages is taken from `HttpServletRequest.getRemoteAddr()`
(the TCP peer address). `X-Forwarded-For` is intentionally ignored to prevent IP spoofing.
If Jahia is behind a trusted reverse proxy, configure `RemoteIpValve` at the Tomcat layer to
rewrite `remoteAddr` to the real client IP before the request reaches the servlet.

### Request body size cap

POST bodies larger than **2 MB** are rejected with a JSON-RPC `-32700` error before any parsing.

## Access control — Allow List

The admin UI at **Administration → MCP Server** lets you restrict which GraphQL operations the MCP server may execute.

### Behaviour

| Whitelist state | Effect |
|---|---|
| Empty | All operations are allowed |
| Non-empty | Only listed operations (and their sub-paths) are allowed |

### Dot-path entries

Entries are dot-separated field paths that mirror the GraphQL selection hierarchy:

| Entry | Covers |
|---|---|
| `admin` | All operations under `admin` |
| `admin.jahia` | All operations under `admin.jahia` |
| `admin.jahia.isAlive` | Only that specific nested field |

Whitelist entries are persisted in the OSGi configuration `org.jahia.community.mcp` and can also be set manually in `META-INF/configurations/org.jahia.community.mcp.cfg`:

```properties
whitelist=jcr,currentUser,admin.jahia.isAlive
```

### Blocked operation log

Every blocked operation is logged at `WARN` level with the operation path, the authenticated user, and the client IP:

```
WARN McpServlet - MCP operation blocked: path='admin.jahia.shutdown', reason=not in the whitelist, user='john', ip='10.0.0.5'
```

## Health check

A `GET /modules/community-mcp` request returns a JSON status response for authenticated users with the `community-mcp` permission:

```json
{"status":"Jahia MCP server running","version":"1.0.0","tools":["executeGraphQL","introspectSchema","listSkills","getSkill"]}
```

## Testing

Docker-based Cypress integration tests live in `tests/`. They require a running Docker environment.

```bash
cd tests
cp .env.example .env          # fill JAHIA_IMAGE and JAHIA_LICENSE
yarn install
./ci.build.sh                  # build the Cypress Docker image
./ci.startup.sh                # start Jahia + run tests + collect results
```

Test results are written to `tests/results/`.

### Test coverage

| Spec | What is tested |
|---|---|
| `01-mcpSettings.cy.ts` | GraphQL settings API: read, write, round-trip, dot-path persistence |
| `02-mcpEndpoint.cy.ts` | MCP endpoint: whitelist enforcement, dot-path coverage, introspection pass-through, 401 for unauthenticated requests |
| `03-mcpSkills.cy.ts` | Skills GraphQL API (save/read/update/delete), MCP `listSkills` and `getSkill` tools, default skill seeding |