craftercms-mcp-server-plugin

russdanner/craftercms-mcp-server-plugin

3.2

If you are the rightful owner of craftercms-mcp-server-plugin and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.

MCP Server for CrafterCMS is designed to facilitate seamless communication and integration between different components of the CrafterCMS ecosystem.

Model Context Protocol (MCP) Server Integration for CrafterCMS

Overview

This plugin is a Spring AI-based MCP (Model Context Protocol) server. This serves as the server side foundation for an AI-enabled content application, orchestrating Large Language Models while securely exposing content, services, and tools through MCP.

Quick Links


What MCP Enables

The Model Context Protocol (MCP) provides a standardized way for LLMs to:

  • Discover available tools, resources, and capabilities
  • Request structured data and perform actions through well-defined interfaces
  • Operate with explicit, auditable context rather than implicit prompt stuffing

Within CrafterCMS, MCP enables:

  • Secure access to CMS content, metadata, and services

  • Controlled execution of server-side tools

  • Clear separation between:

    • LLM reasoning
    • Context exposure
    • Application logic

This architecture makes AI integrations safer, more maintainable, and more extensible than traditional prompt-only approaches.

Presequsits

This project relies on external dependencies. You will need to configure Grape-based dependency downloads and the Groovy sandbox for this functionality to work.

In a test environment, you can disable the sandbox instead of using the whitelist/blacklist features to simplify installation. In CRAFTER_HOME/bin/apache-tomcat/shared/classes/org/crafter/engine/extension/server-config.properties add the following lines:

crafter.engine.groovy.sandbox.enable=false
crafter.engine.groovy.grapes.download.enabled=true

Installation & Configuration

  1. Ensure that the prerequisites are met (see above).
  2. Install this plugin into the project.
  3. Add configuration for authentication site-config.xml for the project:
     <cors>
        <enable>true</enable>
        <accessControlMaxAge>3600</accessControlMaxAge>
        <accessControlAllowOrigin>*</accessControlAllowOrigin>
        <accessControlAllowMethods>GET\, POST\, PUT\, DELETE\, OPTIONS</accessControlAllowMethods>
        <accessControlAllowHeaders>Content-Type\, Mcp-Session-Id\, mcp-protocol-version</accessControlAllowHeaders>
        <accessControlAllowCredentials>true</accessControlAllowCredentials>
    </cors>

    <crafterMcp>
        <allowPublicAccess>false</allowPublicAccess>
        <auth>
            <oauth>
                <mcpServer>
                    <serverUrlBase>http://BASE_URL</serverUrlBase> 

                    <authorizationEndpoint>${crafterMcp.auth.oauth.mcpServer.serverUrlBase}/authorize</authorizationEndpoint>
                    <tokenEndpoint>${crafterMcp.auth.oauth.mcpServer.serverUrlBase}/token</tokenEndpoint>
                    <resourceUrl>${crafterMcp.auth.oauth.mcpServer.serverUrlBase}/api/craftermcp/stream</resourceUrl>            
                </mcpServer>

                <authServer>
                    <serverUrlBase>https://IPD_SERVER/oauth2</serverUrlBase>
                    <userInfoUrlBase>https://IPD_SERVER/oauth2</userInfoUrlBase>
                    <jwksBase>https://IPD_SERVER/JWKS-Base</jwksBase>

                    <clientId>CLIENT-ID</clientId>
                    <secret>SECRET</secret> 

                    <authorizationEndpoint>${crafterMcp.auth.oauth.authServer.serverUrlBase}/authorize</authorizationEndpoint>
                    <tokenEndpoint>${crafterMcp.auth.oauth.authServer.serverUrlBase}/token</tokenEndpoint>
                    <userinfoEndpoint>${crafterMcp.auth.oauth.authServer.userInfoUrlBase}/userInfo</userinfoEndpoint>
                    <jwksUri>${crafterMcp.auth.oauth.authServer.jwksBase}/.well-known/jwks.json</jwksUri> 
                </authServer>
            </oauth>
        </auth>
    </crafterMcp>
  1. Add the MCP server to your application-context.xml for the project:
    <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer" parent="crafter.properties"/>

        <!-- MCP Server -->
    <bean name="crafterMcpServer" class="plugins.org.craftercms.rd.plugin.mcp.server.CrafterMcpServer">
        <property name="previewMode" value="${crafter.security.preview.enabled:false}"/>

        <property name="allowPublicAccess" value="${crafterMcp.allowPublicAccess}"/> 
        <property name="oauthMcpServerUrlBase" value="${crafterMcp.auth.oauth.mcpServer.serverUrlBase}" />
        <!-- property name="oauthMcpServerAuthUrlBase" value="${crafterMcp.auth.oauth.mcpServer.serverUrlBase}" /-->
        <property name="oauthMcpServerAuthorizationEndpoint" value="${crafterMcp.auth.oauth.mcpServer.authorizationEndpoint}" />
        <property name="oauthMcpServerTokenEndpoint" value="${crafterMcp.auth.oauth.mcpServer.tokenEndpoint}" />
        <property name="oauthMcpServerResourceUrl" value="${crafterMcp.auth.oauth.mcpServer.resourceUrl}" />

        <property name="oauthAuthServerUrlBase" value="${crafterMcp.auth.oauth.authServer.serverUrlBase}" />
        <property name="oauthAuthServerAuthorizationEndpoint" value="${crafterMcp.auth.oauth.authServer.authorizationEndpoint}" />
        <property name="oauthAuthServerTokenEndpoint" value="${crafterMcp.auth.oauth.authServer.tokenEndpoint}" />
        <property name="oauthAuthServerUserinfoEndpoint" value="${crafterMcp.auth.oauth.authServer.userinfoEndpoint}" />
        <property name="oauthAuthServerJwksUri" value="${crafterMcp.auth.oauth.authServer.jwksUri}" />
        <property name="oauthAuthServerClientId" value="${crafterMcp.auth.oauth.authServer.clientId}" />
        <property name="oauthAuthServerSecret" value="${crafterMcp.auth.oauth.authServer.secret}" />

        <property name="oauthClientRedirectUrlBase" value="${crafterMcp.auth.oauth.client.redirectUrlBase}" />


        <property name="authValidator">
            <bean name="jwtAuthenticator" class="plugins.org.craftercms.rd.plugin.mcp.server.auth.validator.SimpleAuthValidator">
            </bean>   
        </property>
    </bean>

    <bean name="toolSpringBeanScanner" class="plugins.org.craftercms.rd.plugin.mcp.server.tools.ToolSpringBeanScanner" init-method="scan">
        <property name="mcpServer" ref="crafterMcpServer" />
    </bean>

  1. Configure URL rewrites for OAuth Add the following to urlrewrite.xml
<urlrewrite>


    <rule>
        <from>^/.well-known/oauth-protected-resource(.*)$</from>
        <to type="forward" qsappend="true">/api/plugins/org/craftercms/rd/plugin/mcp/server/craftermcp/protected-resource.json</to>
    </rule>
    <rule>
        <from>^/.well-known/oauth-protected-resource/api/craftermcp/stream(.*)$</from>
        <to type="forward" qsappend="true">/api/plugins/org/craftercms/rd/plugin/mcp/server/craftermcp/protected-resource.json</to>
    </rule>
    <rule>
        <from>^/authorize(.*)$</from>
        <to type="forward" qsappend="true">/api/plugins/org/craftercms/rd/plugin/mcp/server/craftermcp/authorize</to>
    </rule>
    <rule>
        <from>^/.well-known/openid-configuration(.*)$</from>
        <to type="forward" qsappend="true">/api/plugins/org/craftercms/rd/plugin/mcp/server/craftermcp/oauth-config.json</to>
   </rule>
    <rule>
        <from>^/.well-known/openid-configuration/api/craftermcp/stream(.*)$</from>
        <to type="forward" qsappend="true">/api/plugins/org/craftercms/rd/plugin/mcp/server/craftermcp/oauth-config.json</to>
   </rule>

    <rule>
        <from>^/api/craftermcp/stream/.well-known/openid-configuration(.*)$</from>
        <to type="forward" qsappend="true">/api/plugins/org/craftercms/rd/plugin/mcp/server/craftermcp/oauth-config.json</to>
   </rule>
    <rule>
        <from>^/token(.*)$</from>
        <to type="forward" qsappend="true">/api/plugins/org/craftercms/rd/plugin/mcp/server/craftermcp/token</to>
   </rule>


</urlrewrite>  

Example Apache HTTPD Configuration:

<VirtualHost *:80>


    ServerName localhost

#    RewriteRule (.*) $1/?crafterSite=mcptest [QSA,PT]
     Header unset Access-Control-Allow-Origin
     Header unset Access-Control-Allow-Methods
     Header unset Access-Control-Allow-Headers

    Header always set Access-Control-Allow-Origin "*"
    Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS, PATCH"
    Header always set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With, Accept, Origin, Mcp-Session-Id, mcp-protocol-version"
#                                                  "Content-Type, Authorization, X-Requested-With, Accept, Origin, Mcp-Session-Id, mcp-protocol-version" 
    Header always set Access-Control-Expose-Headers "Mcp-Session-Id, Content-Type, mcp-protocol-version"
    Header always set Access-Control-Max-Age "3600"

    RewriteEngine On
    RewriteCond %{REQUEST_METHOD} OPTIONS
    RewriteRule .* - [END]

    RewriteRule ^(.*)$ $1?crafterSite=mcptest [QSA,PT]

    ProxyPass / http://localhost:9080/
    ProxyPassReverse / http://localhost:9080/

</VirtualHost>

Adding Tools

Adding Tools as beans

  1. Declare the tool as a bean
  2. Wire the bean into the MCP Server:
    <bean name="crafterMcpServer" class="org.craftercms.ai.mcp.server.CrafterMcpServer">
        ...
        <property name="mcpTools">
            <list>
                <ref bean="toolIsIngredientAvail" />
                <ref bean="toolGetTemperature" />
            </list>
        </property>
        ...
    </bean>
Wiring to bean and service records with Reflections
    <!-- My Services-->
    <bean name="recipeService" class="foo.RecipeService" />

    <!-- Tools -->
    <bean name="toolIsIngredientAvail" class="org.craftercms.ai.mcp.server.tools.McpToolReflect">
        <property name="serviceObject" ref="recipeService" />
        <property name="methodName" value="isIngredientAvailable" />
        <property name="toolName" value="isIngredientAvailable" />
        <property name="toolDescription" value="returns a response indicating if an ingredient is available or not" />
        <property name="returnType" value="string" />
        <property name="params">
            <list>
                <bean name="param1" class="org.craftercms.ai.mcp.server.tools.McpTool.ToolParam">
                    <property name="name" value="ingrdient" />
                    <property name="type" value="string" />
                    <property name="description" value="The name of the ingredient to check" />
                    <property name="required" value="true" />
                </bean>
            </list>
        </property>
    </bean>

Wiring to an existing REST API
    <bean name="toolGetTemperature" class="org.craftercms.ai.mcp.server.tools.McpToolRest">
        <property name="baseUrl" value="http://localhost:8080" />
        <property name="url" value="/api/foo/temperature.json" />
        <property name="toolName" value="getTemperature" />
        <property name="toolDescription" value="Returns the temperature" />
        <property name="returnType" value="string" />
        <property name="previewToken" value="${ai.crafterPreviewToken}" />
        <property name="params">
            <list>
                <bean name="param1" class="org.craftercms.ai.mcp.server.tools.McpTool.ToolParam">
                    <property name="name" value="city" />
                    <property name="type" value="string" />
                    <property name="description" value="The name of the city to check" />
                    <property name="required" value="true" />
                </bean>
            </list>
        </property>
    </bean>

Wiring tools via Open API spec

    <bean name="toolOpenApiSpecParser" class="org.craftercms.ai.mcp.server.tools.ToolOpenApiSpecParser" init-method="parse">
        <property name="baseUrl" value="http://localhost:8080" />
        <property name="openApiSpecUrl" value="https://raw.githubusercontent.com/craftercms/engine/refs/tags/v4.4.2/src/main/api/engine-api.yaml"/>
        <property name="mcpServer" ref="crafterMcpServer" />
        <property name="includeTags">
            <list>
                <value>content</value>
            </list>
        </property>
        <property name="excludeTags">
            <list />
        </property>
    </bean>

Using Annotations

package org.acme

import plugins.org.craftercms.rd.plugin.mcp.server.tools.DeclareTool
import plugins.org.craftercms.rd.plugin.mcp.server.tools.DeclareToolParam
 
public class AcmeAirlineServices {

    @DeclareTool(toolName="bookFlight", returnType="string", toolDescription="Book a specific seat on a given flight", scopes="custom:Wallet, profile, email" )
    @DeclareToolParam (name="flight", type="string", description="The flight the user wants")
    @DeclareToolParam (name="seat", type="string", description="The seat the user wants")
    public String bookFlight(String flight, String seat) {
        return "Booked"
    } 
}     

Note: Don't forget to declare the bean in your applicaiton-context.xml

    <bean name="acmeAirlineServices" class="org.acme.AcmeAirlineServices" />

Alternative approach (declare bean via Spring annotations) Currently blocked by: https://github.com/craftercms/craftercms/issues/8375

package org.acme

import org.springframework.stereotype.Component
import org.springframework.context.annotation.Lazy

import plugins.org.craftercms.rd.plugin.mcp.server.tools.DeclareTool
import plugins.org.craftercms.rd.plugin.mcp.server.tools.DeclareToolParam

@Component("acmeAirlineServices")
@Lazy(false) 
public class AcmeAirlineServices {

    @DeclareTool(toolName="bookFlight", returnType="string", toolDescription="Book a specific seat on a given flight", scopes="custom:Wallet, profile, email" )
    @DeclareToolParam (name="flight", type="string", description="The flight the user wants")
    @DeclareToolParam (name="seat", type="string", description="The seat the user wants")
    public String bookFlight(String flight, String seat) {
        return "Booked"
    } 
}   

Note: Don't forget to add the Spring context scanning to your Application Context

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans.xsd 
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.acme"/>

Adding Resources

More to come on this

Adding Prompts

More to come on this

Connecting with the MCP Inspector

  1. Start the inspector by executing npx @modelcontextprotocol/inspector
  2. Configure Transport Type with the auth URL Streamable HTTP
  3. Configure URL with the auth URL http://localhost/api/plugins/org/craftercms/rd/plugin/mcp/server/craftermcp/stream
  4. If you are requiring authentication, configure your OID/OAuth client ID