Skip to content

MCP

Integrate Model Context Protocol in your workflow

Introduction

The Model Context Protocol (MCP) is a standardized protocol that enables AI models to access external tools and resources through a secure, capability-based negotiation system. This protocol bridges the gap between AI models and external systems, allowing for dynamic tool discovery, execution, and integration.

Module Suite plays a crucial role in seamlessly incorporating MCP into your enterprise content management ecosystem. By bridging the gap between your organization's content repositories and MCP-compatible servers, Module Suite empowers you to:

  1. Extend AI capabilities: Leverage external tools and services through MCP servers, expanding the functionality available to AI models.

  2. Standardize tool integration: Use a consistent protocol for connecting AI models with external resources, simplifying integration complexity.

  3. Enable dynamic tool discovery: Automatically discover and utilize tools available on MCP servers without hardcoding integrations.

  4. Enhance AI workflows: Integrate MCP tools with OpenAI function calling, allowing AI models to automatically execute external tools based on user requests.

  5. Maintain security: Implement secure authentication mechanisms (OAuth2 or custom) to protect access to external tools and resources.

  6. Support flexible authentication: Choose between OAuth2 automatic authentication or custom authorization mechanisms based on your security requirements.

By integrating MCP through Module Suite, organizations can harness the power of standardized tool integration to transform their AI-powered workflows, leading to increased flexibility, reduced integration complexity, and improved overall productivity.

MCP Integration Considerations

While MCP offers significant benefits, it's important to consider factors such as authentication requirements, server compatibility, and network access when integrating MCP servers into your workflow. Module Suite provides the necessary tools and interfaces to address these considerations effectively.

MCP server definition

In Module Suite 4.1.0 (Polaris), a developer can define an MCP server with a DSL. The definition builds a manifest. The manifest is the contract for that server: it records the tools, resources, and prompts the server exposes. A Generation run can call those tools.

The server can call remote tools. A remote tool keeps its name, route, and profile in the manifest, and runs a closure of the same name in a Content Server REST script. Each call runs as the caller. The same definition can run a script on Module Suite Runtime instead of a remote closure.

The server can expose virtual resource folders on Content Server. A virtual folder is listed on Content Server by default, under the permissions of the caller.

flowchart TD
    DSL["manifest.cs: mcpserver DSL"] --> Manifest["Manifest (contract)"]
    Manifest --> Tools[Tools]
    Manifest --> Resources["Resources and resource folders"]
    Manifest --> Prompts[Prompts]
    Tools --> RemoteTool["remote(route, profile)"]
    Tools --> ScriptTool["script(path)"]
    RemoteTool --> Closure["Closure of the same name in a Content Server REST script"]
    ScriptTool --> Runtime["Script on Module Suite Runtime"]

The DSL in manifest.cs builds one manifest that lists the tools, resources, and prompts of the server. Each tool runs in exactly one way: a closure on Content Server, or a script on Module Suite Runtime.

The mcpserver service

The mcpserver service is the entry point of the DSL. It creates the definition builder, the tool builder, and the resource, prompt, and resource folder definitions.

Method Description Since
newDefinitionBuilder() Creates a builder for the server definition that manifest.cs returns. Returns a CSMcpServerDefinitionBuilder. 4.1.0
newToolBuilder(profileId) Creates a tool builder for server-side authoring. profileId is optional. Returns a CSMcpServerToolBuilder. 4.0.0
newResource(name, uri, script, mimeType) Creates a static resource definition. script is a script path under resources/; mimeType is optional. Returns a CSMcpServerResource. 4.0.0
newPrompt(name, script, description) Creates a prompt definition. script is a script path under prompts/; description is optional. Returns a CSMcpServerPrompt. 4.0.0
newResourceFolder(name) Creates a virtual resource folder: Content Server documents listed for each caller. name is a valid closure name. Complete the folder with script(...) or remote(...). Returns a CSMcpResourceFolder. 4.1.0
fromOpenAIFunction(function, profileId) Converts an OpenAI response function to an MCP tool that has no implementation. Deprecated since 4.1.0. 4.0.0

mcpserver.fromOpenAIFunction is deprecated

Since 4.1.0, mcpserver.fromOpenAIFunction(function, profileId) is deprecated, because the tool it returns has no implementation. Use mcpserver.newToolBuilder().fromOpenAIFunction(function), then script(...) or remote(...).

The definition builder

mcpserver.newDefinitionBuilder() returns a CSMcpServerDefinitionBuilder. Return the result of build() from manifest.cs.

Method Description Since
version(version) Sets the version of the server definition. The default is 1.0. 4.1.0
addTool(tool) Adds a tool built with mcpserver.newToolBuilder(), with a script or a remote route. 4.1.0
addResource(resource) Adds a static resource built with mcpserver.newResource(...) and served by a script on Module Suite Runtime. 4.1.0
addPrompt(prompt) Adds a prompt built with mcpserver.newPrompt(...) and served by a script on Module Suite Runtime. 4.1.0
addResourceFolder(folder) Adds a virtual resource folder built with mcpserver.newResourceFolder(...). 4.1.0
build() Builds the server definition: a Map with version, tools, resources, prompts, and resourceFolders. 4.1.0

What the manifest records

The manifest is a Groovy Map with version, tools, resources, prompts, and resourceFolders. The definition builder returns exactly that map, so a hand-written map manifest stays valid.

The manifest runs once, in system context and without a caller. Its result is cached for every caller until the script changes. For this reason the manifest holds declarations only: names, descriptions, schemas, routes, and profiles. It stores names, not closures. A remote tool entry records its route and profile, and the engine calls Content Server at each tool call, as the caller.

Every manifest is validated when it loads:

  • Each tool has a name and exactly one of script or remote. A remote tool has a route.
  • Tool names and folder names are unique, and a folder cannot have the name of a tool.
  • A folder name is a valid closure name ([A-Za-z_][A-Za-z0-9_]*).
  • A static resource cannot use the otcs:// scheme.

The manifest has no caller

Do not call Content Server services from manifest.cs, and do not capture a service in a closure for later use. Declare routes and profiles by name and let the engine make the call for each caller. A manifest that fails or breaks a rule answers every request with the exact problem, and does not run again until it changes.

Remote tools

A remote tool is two declarations: the contract in the manifest, and a closure on Content Server. When the tool is called, the engine posts the arguments to the Content Server REST script <route>/<toolName> with the tool's profile, as the MCP caller. All arguments travel as one JSON form field, mcpArguments. The REST script defines one closure per tool, named like the tool, and each closure takes one Map argument: { Map args -> }. The Content Server REST script uses the AMMcpController behaviour, described below. For background on REST scripts and behaviours, see Content Script REST services. A closure returns plain values (maps, lists, strings, numbers), and no closure can be called start.

The Content Server script always answers with an envelope: {ok: true, result} or {ok: false, error: {code, message}}. A missing closure, a failing closure, or a result that cannot be serialized reaches the MCP client as a readable tool error.

sequenceDiagram
    participant Caller as MCP client or Generation run
    participant Runtime as Module Suite Runtime
    participant CS as Content Server REST script

    Caller->>Runtime: Call remote tool with arguments
    Runtime->>Runtime: Read route and profile from the manifest
    Runtime->>CS: Post mcpArguments to route/toolName, as the caller
    CS->>CS: Run the closure of the same name with a Map
    alt Closure succeeds
        CS-->>Runtime: ok true with result
        Runtime-->>Caller: Tool result
    else Closure fails or is missing
        CS-->>Runtime: ok false with code and message
        Runtime-->>Caller: Tool error with code and message
    end

The caller only sees the tool contract. The call to Content Server runs as the caller, so Content Server permissions apply to the closure and to everything it reads.

The AMMcpController behaviour

AMMcpController (package com.answer.modules.behaviours) turns a Content Script REST service into the Content Server side of an MCP server defined on Module Suite Runtime. Add its behaviours to the REST script and return start():

extract_word_revisions_and_comments = { Map args -> [revisions: ...] }

if(!BehaviourHelper.hasBehaviour(this, "start")) {
    BehaviourHelper.addBeahaviours(this, AMMcpController.getBehaviours())
}
return start()

Note

addBeahaviours is the actual name of the BehaviourHelper method. Type it exactly as shown.

The contract of the behaviour is:

  • One closure per tool or folder. Every closure the script defines at top level is callable as POST <route>/<closureName>. The engine calls a remote tool by its tool name and a remote resource folder by its folder name. The behaviour looks the closure up in the script binding, so assign it without def. A closure name matches [A-Za-z_][A-Za-z0-9_]*, and start is reserved for the behaviour.
  • One argument field. The closure receives one Map with the arguments, decoded from the JSON form field mcpArguments. The map is empty when the field is empty, and always empty for a folder. The field must be a JSON object.
  • Plain return values. A tool closure returns maps, lists, strings, numbers, and booleans. A folder closure returns a List of Map with nodeId, name, and an optional mimeType.
  • Always an envelope. The answer is always HTTP 200 with a JSON envelope, {ok: true, result} or {ok: false, error: {code, message}}, so the caller receives the error instead of a failed request.
Error code When
missing_tool The path is not <script>/<toolName>, the name is not a valid closure name, or it names start.
unknown_tool The script has no closure with that name.
invalid_arguments mcpArguments is not valid JSON, for example because a form size limit cut it, or it is not a JSON object.
tool_failed The closure threw an exception. The message is the exception message.
result_not_serializable The closure returned a value that cannot be converted to JSON.

On Module Suite Runtime, ok: true gives the result as the tool result, and ok: false gives a tool error with <code>: <message>. Any other answer gives the tool error "Content Server did not answer with a tool envelope".

Example: A Content Server REST script for remote tools

import com.answer.modules.behaviours.AMMcpController
import com.answer.modules.behaviours.BehaviourHelper

def CONTRACTS_FOLDER_ID = 12345L   // folder listed by the 'contracts' resource folder

find_contract_tasks = { Map args ->
    if (args.contractId && !(args.contractId ==~ /\d+/)) {
        throw new IllegalArgumentException('contractId must be a node ID')
    }
    if (args.status && !(args.status in ['READY', 'IN_PROGRESS', 'COMPLETED', 'OVERDUE'])) {
        throw new IllegalArgumentException("Unknown status ${args.status}")
    }
    def tasks = []   // search the contract tasks here, as the caller
    return [contractId: args.contractId, status: args.status, tasks: tasks]
}

contracts = { Map args ->
    docman.getNodeFast(CONTRACTS_FOLDER_ID).childrenFast
        .findAll { it.subtype == 144 }
        .collect { [nodeId: it.ID, name: it.name, mimeType: it.mimeType] }
}

if (!BehaviourHelper.hasBehaviour(this, "start")) {
    BehaviourHelper.addBeahaviours(this, AMMcpController.getBehaviours())
}
return start()

This script is the clm REST script of the manifest example below. find_contract_tasks implements the remote tool of the same name and checks its own arguments; an invalid argument reaches the client as a tool_failed error. contracts lists the documents of the contracts resource folder, under the caller's permissions.

Local tools

A tool built with script(path) runs a script on Module Suite Runtime. The script receives arguments (a Map), params, and serverId. Its return value is data and is never read as an envelope:

  • A String stays text. null is empty text, and numbers and booleans become their text.
  • A List becomes JSON text.
  • A Map becomes JSON text plus structuredContent.
  • An exception becomes a tool error (isError).

An unknown tool, an invalid manifest, and an authorization request stay protocol errors.

The tool builder

mcpserver.newToolBuilder() returns a CSMcpServerToolBuilder. It extends the MCP tool builder, so the methods since 3.9.0 return the server tool builder for chaining. A tool needs a name and either a script or a remote route.

Method Description Since
name(name) Sets the name of the tool. A remote tool runs the closure with this name. 3.9.0
title(title) Sets the title of the tool, shown in the tool list. 3.9.0
description(description) Sets the description of the tool. 3.9.0
inputSchema(inputSchema) Sets the input schema, as a CSMcpJsonSchema or a Map. 3.9.0
outputSchema(outputSchema) Sets the output schema, as a Map. A tool with an output schema must return a Map. 3.9.0
meta(meta) Sets the metadata of the tool, as a Map. 3.9.0
fromOpenAIFunction(function) Copies the name, description, and parameters of a function built with openai.newResponseFunctionBuilder(). A parameter that the function declares as nullable becomes an optional MCP parameter. 4.1.0
script(path) Runs the tool with a script of this MCP server on Module Suite Runtime. path is relative to the server folder, for example tools/summarize.cs. 4.1.0
remote(route) Runs the tool with the closure of the same name in the Content Server REST script route under CSServices, as the MCP caller, with the default csrest profile. 4.1.0
remote(route, profileId) Same as remote(route), with the given csrest profile. The profile must be listed in amcs.csrest.activeProfiles. 4.1.0
build() Builds the tool. Returns a CSMcpServerTool to add with addTool(...). 4.1.0

A built CSMcpServerTool exposes script and route (one of them is null), and toManifestEntry() returns its manifest entry.

Permissions

Use a delegated profile on remote tools and resource folders, so that each call runs as the MCP caller and Content Server permissions apply. A folder whose profile is not delegated shows that account's documents to every caller. Such a folder must be acknowledged with sharedAccount(); otherwise it is refused.

To check a profile from a script, call isDelegatedProfile(profileId) on any remote Content Server REST service, for example rcsnodes.isDelegatedProfile("default"). It returns true when the profile authenticates as the current caller (authMode delegated). The profile must be listed in amcs.csrest.activeProfiles; otherwise the call throws ConfigurationException. Since 4.1.0.

Arguments are not checked against the schema

The input schema helps the client build a call. Module Suite Runtime does not validate the arguments against it, so every tool script and every Content Server closure validates its own arguments.

Resources and prompts

A static resource is declared with mcpserver.newResource(name, uri, script, mimeType) and is read by a script under resources/. A prompt is a script under prompts/ that the manifest lists by name. Declare it with mcpserver.newPrompt(name, script, description) and add it with addPrompt(...). There is no prompt builder: a CSMcpServerPrompt only holds name, script, and description.

mcpserver.newPrompt('summarize', 'prompts/summarize.cs', 'Summarize text')

This example declares a prompt named summarize that the script prompts/summarize.cs generates.

A virtual resource folder lists Content Server documents for each caller. Its documents appear in the resource list as otcs://<folder>/<nodeId>:

  • newResourceFolder(name).remote(route[, profile]) calls the closure named like the folder in the Content Server REST script. The closure lists documents as maps with nodeId, name, and an optional mimeType. This is the default way to list a folder.
  • newResourceFolder(name).script(path[, profile]) lists documents with a script on Module Suite Runtime, for example from a search or from several sources. The listing script must not change the profile: the engine selects the folder's profile first and refuses a listing that changes it.
flowchart LR
    Client["MCP client"] --> List["List resources"]
    List --> Folder["Virtual resource folder"]
    Folder --> Listing["Folder closure on Content Server"]
    Listing --> Perms["Caller's permissions"]
    Perms --> Entries["otcs://folder/nodeId entries"]
    Entries --> Read["Read resource"]
    Read --> Relist["Listing runs again for the caller"]
    Relist --> Download["Latest version downloaded through the Content Server REST API"]

A folder is listed under the caller's permissions, so each caller sees only the documents that caller can see. A read runs the listing again for that caller and serves only a listed node.

A read takes the MIME type and size from the latest version and refuses documents over mcp.resources.max-bytes before it downloads them. It returns text for text/*, JSON, and XML types, and a base64 blob for other types.

mcpserver.newResourceFolder(name) returns a CSMcpResourceFolder:

Method Description Since
remote(route) Lists the documents with the closure named like the folder in the Content Server REST script route, as the MCP caller, with the default csrest profile. The script uses the AMMcpController behaviour. 4.1.0
remote(route, profileId) Same as remote(route), with the given csrest profile. The profile must be listed in amcs.csrest.activeProfiles. 4.1.0
script(path) Lists the documents with a script of this MCP server on Module Suite Runtime, with the default csrest profile. The script returns a List of Map with nodeId, name, and an optional mimeType, and must not call useProfile. 4.1.0
script(path, profileId) Same as script(path), with the given csrest profile. 4.1.0
sharedAccount() Acknowledges that the folder profile is not delegated, so every MCP caller sees the documents that account lists. 4.1.0
toManifestEntry() Returns the manifest entry of the folder: a Map with name, profile, sharedAccount, and either script or remote. 4.1.0

The folder also exposes the fields name, script, route, profile, and sharedAccount.

These Module Suite Runtime properties limit remote tools and resource folders:

Property Default Meaning
mcp.remote.max-argument-bytes 1048576 Largest JSON argument payload sent to Content Server.
mcp.resources.max-bytes 10485760 Largest document that a resource read returns.
mcp.resources.folder-max-entries 1000 Most documents that one folder lists.

Example: Building a tool

A tool in the manifest is built with the MCP server tool builder:

mcpserver.newToolBuilder()

This example starts a tool definition. From that builder, script(path) runs a script on Module Suite Runtime, and remote(route[, profile]) runs the closure of the same name in a Content Server REST script. The profile on a remote tool is optional.

Example: A manifest with remote and local tools

def ROUTE = 'clm'          // REST script on Content Server
def PROFILE = 'default'    // csrest profile, configured as delegated

def findTasks = openai.newResponseFunctionBuilder()
    .name('find_contract_tasks')
    .description('Read-only search of contract tasks, optionally by contract or status.')
    .addStringParameter('contractId', 'Contract workspace ID.', false)
    .addStringParameter('status', 'READY, IN_PROGRESS, COMPLETED or OVERDUE.', false)
    .build()

def summarize = openai.newResponseFunctionBuilder()
    .name('summarize_text')
    .description('Summarizes the supplied text in at most five bullets.')
    .addStringParameter('text', 'The text to summarize.', true)
    .build()

return mcpserver.newDefinitionBuilder()
    .version('1.0')
    .addTool(mcpserver.newToolBuilder().fromOpenAIFunction(findTasks)
        .title('Find contract tasks').remote(ROUTE, PROFILE).build())
    .addTool(mcpserver.newToolBuilder().fromOpenAIFunction(summarize)
        .title('Summarize text').script('tools/summarize_text.cs').build())
    .addResource(mcpserver.newResource('Handbook', 'file:///kb/handbook.pdf', 'resources/handbook.cs', 'application/pdf'))
    .addPrompt(mcpserver.newPrompt('summarize', 'prompts/summarize.cs', 'Summarize text'))
    .addResourceFolder(mcpserver.newResourceFolder('contracts').remote(ROUTE, PROFILE))
    .build()

This manifest declares a remote tool that runs the find_contract_tasks closure of the clm REST script, a local tool that runs tools/summarize_text.cs on Module Suite Runtime, a static resource, a prompt, and a virtual resource folder listed by the contracts closure of the same REST script. fromOpenAIFunction copies the name, description, and parameters of the function. A parameter declared as optional stays optional in MCP.

Architecture and Networking

Module Suite acts as a central hub for communication between Extended ECM (xECM), MCP-compatible servers, and AI services.

Here's an overview of how the networking and communication work:

flowchart TD
    subgraph ECM["Extended ECM (xECM)"]
        MS[Module Suite]
    end

    MCP[MCP Server]
    OAuth[OAuth Provider]
    OpenAI[OpenAI Service]

    MS <--> |Internal APIs| ECM
    MS <--> |MCP Protocol| MCP
    MS <--> |OAuth 2.0| OAuth
    MS <--> |Function Calling| OpenAI

    style ECM fill:#f9f,stroke:#333,stroke-width:2px
    style MS fill:#bbf,stroke:#333,stroke-width:2px
    style MCP fill:#bfb,stroke:#333,stroke-width:2px
    style OAuth fill:#fbb,stroke:#333,stroke-width:2px
    style OpenAI fill:#fbb,stroke:#333,stroke-width:2px

Integration with xECM

Module Suite runs directly on xECM, providing seamless access to all xECM APIs. This tight integration allows for efficient data exchange and leveraging of xECM's content management capabilities.

MCP Server Communication

Module Suite implements communication channels to MCP-compatible servers, which can be: - Public internet services - VPN-accessible services - On-premises solutions

The mcp Content Script service includes methods to programmatically connect to MCP servers, list available tools, execute tools, and convert MCP tools to OpenAI function calling format to be used with the openai service directly.

MCP Protocol Support

Module Suite features comprehensive support for the Model Context Protocol specification, enabling flexible integration with various MCP-compatible servers. This allows for standardized tool integration while maintaining a consistent interface.

Authentication Mechanisms

Module Suite supports two authentication modes for MCP integration:

  1. OAuth2 Authentication (automatic): Full OAuth2 flow with automatic token management
  2. Custom Authorization (manual): Flexible authentication using custom headers or tokens
Typical Communication Sequence

Below is a diagram illustrating a typical communication sequence when using Module Suite with xECM, an MCP server, and OpenAI:

sequenceDiagram
    participant User
    participant xECM
    participant Module Suite
    participant MCP Server
    participant OAuth Provider
    participant OpenAI

    User->>xECM: Request AI operation
    xECM->>Module Suite: Pass request
    Module Suite->>Module Suite: Check authentication
    alt Authentication required
        Module Suite->>OAuth Provider: Request authorization
        OAuth Provider-->>Module Suite: Return tokens
    end
    Module Suite->>MCP Server: List available tools
    MCP Server-->>Module Suite: Return tool definitions
    Module Suite->>OpenAI: Send request with MCP tools
    OpenAI-->>Module Suite: Request tool execution
    Module Suite->>MCP Server: Execute tool
    MCP Server-->>Module Suite: Return tool result
    Module Suite->>OpenAI: Return tool result
    OpenAI-->>Module Suite: Return final response
    Module Suite->>xECM: Process and format response
    xECM->>User: Display result

Components of the MCP Service Integration

Module Suite provides a comprehensive set of components to enable seamless integration with MCP services. These components work together to offer a robust and flexible MCP-enhanced experience within the Extended ECM environment.

Content Script Service

MCP Extension Package Service

The MCP service is a dedicated Content Script extension package service specifically designed for Model Context Protocol integrations. Key features include:

  • Multi-profile support for flexible configuration
  • Comprehensive implementation of MCP protocol features
  • OAuth2 and custom authorization support
  • Tool discovery and execution capabilities
  • OpenAI function calling integration
graph TD
    A[Module Suite] --> B[MCP Service]
    B --> C[Tool Discovery]
    B --> D[Tool Execution]
    B --> E[OpenAI Integration]
    B --> F[Authentication]
    F --> G[OAuth2]
    F --> H[Custom Authorization]

    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#bbf,stroke:#333,stroke-width:2px

Integration Use Cases

Module Suite offers various capabilities for integrating MCP-powered functionalities into your Extended ECM environment. Let's explore common use cases and how to implement them.

Tool Discovery

Tool discovery allows you to explore and understand the capabilities available on an MCP server. This functionality is valuable for implementing:

  • Dynamic tool integration without hardcoding
  • Capability-based feature negotiation
  • Tool documentation and schema inspection
  • Integration planning and development

Example: Listing Available Tools

Here's a simple example of how to discover tools available on an MCP server:

try {
    mcp.validateProfile("mcp-profile")
    def toolsResult = mcp.listTools("mcp-profile")

    toolsResult.tools.each { tool ->
        out << "<p><b>${tool.name}</b>: ${tool.description}</p>"
    }
} catch (Exception e) {
    log.error("Error listing tools: ${e.message}", e)
    out << "An error occurred: " + e.getMessage()
}

This code validates the MCP profile configuration, retrieves the list of available tools from the MCP server, and displays each tool's name and description.

The listTools method returns a result object containing all tools available on the MCP server. Each tool includes metadata such as name, description, and parameter schemas.

Use Autocompletion

Remember to use the auto-completion feature of the editor (CTRL+Space) to explore available configuration options when working with the MCP service.

Tool Execution

Tool execution allows the AI to interact directly with external systems through MCP servers, performing actions or retrieving information as needed. This powerful feature enables the AI to manipulate content and execute operations within external systems.

Example: Executing a Tool

In this example, we'll demonstrate how to execute a tool on an MCP server:

try {
    mcp.validateProfile("mcp-profile")

    def result = mcp.executeTool(
        "search_documents",
        [
            query: "contract",
            limit: 10
        ],
        "mcp-profile"
    )

    result.content.each { content ->
        if (content.type == "text") {
            out << "<p>${content.text}</p>"
        }
    }
} catch (Exception e) {
    log.error("Error executing tool: ${e.message}", e)
    out << "An error occurred: " + e.getMessage()
}

This example demonstrates several key concepts:

  • Profile Validation: The validateProfile method ensures the MCP profile is properly configured and authenticated.

  • Tool Execution: The executeTool method takes the tool name, parameters, and profile ID to execute the tool on the MCP server.

  • Result Processing: The method returns a CSMcpCallToolResult object that contains:

    • content: List of content items returned by the tool (text, image, audio, resource, or resource_link)
    • isError: Boolean indicating if the tool execution resulted in an error
    • structuredContent: Optional JSON object representing the structured result of the tool call
    • meta: Map containing metadata for the result
  • Error Handling: The implementation includes error handling to manage potential issues during tool execution.

Tool Parameter Validation

Ensure that tool parameters match the schema defined by the MCP server. Invalid parameters will result in tool execution failures.

MCP Tools with OpenAI Integration

MCP tools can be seamlessly integrated with OpenAI function calling, allowing AI models to automatically discover and execute external tools based on user requests. This integration enables powerful workflows where AI models can leverage external capabilities dynamically.

Example: Using MCP Tools with OpenAI

def profileId = 'mcp-profile'

try {
    // Validate profile
    mcp.validateProfile(profileId)

    // Get all MCP tools as OpenAI response functions
    def tools = mcp.listToolsAsOpenAIResponseTools(profileId)

    // Build OpenAI response request with all tools
    def responseBuilder = openai.newResponseRequestBuilder()
        .model("gpt-4o")
        .instructions("You are a helpful assistant.")
        .input("Write a file and list all files in the directory")
        .toolChoiceMode("auto")

    // Add all tools as function tools
    tools.each { tool -> responseBuilder.addFunctionTool(tool) }

    def request = responseBuilder.build()
    def result = openai.createResponse(request)

    // Process response types
    def responseTypes = result.getResponseType()

    responseTypes.eachWithIndex { type, index ->
        if (type == "function_call") {
            // Handle function call - executor automatically executes MCP tool
            result.handleFunctionCall({ functionCallId, callId, status, name, arguments ->
                def selectedTool = tools.find { it.name == name }
                def functionResult = selectedTool?.executor(arguments)
                return functionResult
            }, index)
        }
    }
} catch (Exception e) {
    log.error("Error in OpenAI integration: ${e.message}", e)
    out << "An error occurred: " + e.getMessage()
}

This example demonstrates the powerful integration between MCP and OpenAI:

  • Tool Conversion: The listToolsAsOpenAIResponseTools method converts MCP tools into OpenAI function calling format, making them available to AI models.

  • Automatic Tool Discovery: All available MCP tools are automatically added to the OpenAI request, allowing the AI to choose which tools to use based on the user's request.

  • Automatic Execution: When the AI decides to call a function, the executor automatically executes the corresponding MCP tool, handling the complexity of tool invocation.

  • Seamless Integration: The integration is transparent to the AI model, which simply sees function calling capabilities without needing to know about the underlying MCP protocol.

Benefits of MCP-OpenAI Integration

This integration offers several advantages:

  1. Dynamic Tool Discovery: AI models can automatically discover and use new tools as they become available on MCP servers.

  2. Standardized Interface: MCP provides a standardized way to expose tools, making integration consistent across different services.

  3. Flexible Execution: Tools can be executed automatically based on AI decisions, reducing the need for manual intervention.

  4. Extensibility: New tools can be added to MCP servers without requiring changes to the AI integration code.

Tool Configuration

When creating a new MCP tool execution, it is possible to provide the tool configuration in the form of a Map object. The structure of this map is compatible with the JSON format MCP uses to define tool calls. This is the best approach to handle complex tool executions.

Core Concepts

Capability-Based Negotiation

The Model Context Protocol uses a capability-based negotiation system where clients and servers explicitly declare their supported features during initialization. Capabilities determine which protocol features and primitives are available during a session.

  • Servers declare capabilities like tool support
  • Clients declare capabilities for tool invocation and execution
  • Both parties must respect declared capabilities throughout the session
  • Additional capabilities can be negotiated through extensions to the protocol

The following diagram illustrates the MCP session lifecycle with tool listing and execution:

sequenceDiagram
    participant Client as Client
    participant Server as Server

    Client->>Server: Initialize client
    Server->>Client: Initialize session with capabilities
    Client->>Server: Respond with supported capabilities

    Client->>Server: List tools
    Server->>Client: Return available tools

    loop Tool Execution
        Client->>Server: Execute tool with parameters
        Server->>Client: Return tool result
    end

    Client->>Server: Terminate
    Server->>Client: End session

Each capability unlocks specific protocol features for use during the session. For example:

  • Implemented server features must be advertised in the server's capabilities
  • Tool invocation requires the server to declare tool capabilities
  • Tool listing requires the server to declare tool support in its capabilities

This capability negotiation ensures clients and servers have a clear understanding of supported functionality while maintaining protocol extensibility.

Tools

One of the core concepts when working with MCP is the "Tool", which represents a callable function that can be executed on the MCP server.

When defining a tool execution, you will be able to provide the tool name and parameters. The tool execution returns results that can contain text, images, resources, or other content types.

MCP tools can also be converted to OpenAI function calling format, allowing AI models to automatically execute tools through the MCP service.

Configuration

Base Configuration parameters description, default and purpose.

MCP Service Configuration Overview

The Module Suite Extension for MCP supports two authentication modes:

  1. OAuth2 Authentication (automatic): Requires full OAuth configuration
  2. Custom Authorization (manual): Only requires the MCP base URI, OAuth can be bypassed

OAuth2 Configuration

If you want to use OAuth2 automatic authentication, it is mandatory to set an mcp profile in the Module Suite Base Configuration with the following properties:

Property Description Example Value
mcp_uri MCP server base URI https://mcp.example.com/mcp
client_id OAuth2 client ID your-client-id
client_secret OAuth2 client secret your-secret-key
auth_uri OAuth2 authorization endpoint https://auth.example.com/oauth/authorize
token_uri OAuth2 token endpoint https://auth.example.com/oauth/token
refresh_uri OAuth2 refresh token endpoint https://auth.example.com/oauth/refresh
redirect_uri Content Server redirect URL https://your-domain:8443/otcs/cs
scope OAuth2 scope read write or empty
additional_props Additional properties specific for MCP additional_key@additional_value

OAuth2 Flow

The extension handles the OAuth authentication flow to MCP but the first time an mcp profile is used, it is necessary to authenticate the application on the MCP server website.

Redirect URI Configuration

The redirect_uri should point to your Content Server instance and will be used during the OAuth authorization flow. Ensure this URL is accessible and properly configured in your OAuth provider.

Custom Authorization Configuration

OAuth can be bypassed by providing an authorization closure to the validateProfile method. This allows authentication with any server using custom headers or authentication mechanisms. When using custom authorization, the minimum required configuration is:

Property Description Example Value
mcp_uri MCP server base URI https://mcp.example.com/mcp

All other OAuth-related properties (client_id, client_secret, auth_uri, etc.) are only required for OAuth automatic authentication and are not used when using custom authorization.

Security Considerations

When using custom authorization, ensure that your authentication mechanism is secure and follows your organization's security policies. API keys and tokens should be stored securely and not exposed in logs or public configurations.

Authentication

The extension supports two authentication modes: OAuth2 automatic authentication and custom authorization via closure.

OAuth2 Authentication

The MCP integration uses OAuth 2.0 for secure authentication. The following sequence diagram illustrates the complete authentication flow:

sequenceDiagram
    participant User as User
    participant CS as Content Server
    participant OAuth as OAuth Provider
    participant MCP as MCP Server

    User->>CS: Request MCP operation
    CS->>CS: Check for valid access token
    alt No valid access token
        CS->>CS: Check for refresh token
        alt No refresh token
            CS->>User: Redirect to authorization
            User->>OAuth: Authorize application
            OAuth->>CS: Return authorization code
            CS->>OAuth: Exchange code for tokens
            OAuth->>CS: Return access & refresh tokens
            CS->>CS: Store tokens securely
        else Refresh token exists
            CS->>OAuth: Request new access token
            OAuth->>CS: Return new access token
            CS->>CS: Store updated token
        end
        CS->>MCP: Make API request with token
        MCP->>CS: Return response
        CS->>User: Return result
    else Valid access token exists
        CS->>MCP: Make API request with token
        MCP->>CS: Return response
        CS->>User: Return result
    end

Token Management

The extension automatically handles token refresh:

  • Access tokens are refreshed automatically when expired
  • Refresh tokens are used to obtain new access tokens
  • Tokens are stored securely in Content Server's system data
  • Automatic refresh occurs transparently during API calls

Custom Authorization

OAuth can be bypassed by providing an authorization closure to the validateProfile method. This allows authentication with any server using custom headers, API keys, or other authentication mechanisms. When using custom authorization, only the mcp_uri configuration property is required.

EXAMPLE: Authorizing the application on the MCP endpoint
def profileId = 'mcp-profile'

try {
    mcp.validateProfile(profileId)
    out << "Successfully logged on MCP with the profile <b>${profileId}</b>"
} catch(AuthenticationException e) {
    def redirectUrl = "${url}/runcs/${self.ID}"
    def authURL = mcp.getAuthorizationUrl(redirectUrl, profileId)
    redirect authURL
}

The script invokes the Module Suite Extension for MCP validateProfile method which checks the configuration properties for the provided profile ID, checks the authorization code and, if it doesn't exists in the system, throws an AuthenticationException to indicate that the login to MCP is required. This exception is designed to be caught so that the authorization process can be initiated by redirecting the user to the OAuth authorization URL.

EXAMPLE: Using custom authorization
def profileId = 'mcp-profile'

// Define custom authorization closure
Closure authorizationClosure = { builder ->
    builder.header("OTCSTicket", "xxx-token-here")
}

// Validate profile with custom authorization (OAuth is bypassed)
mcp.validateProfile(profileId, authorizationClosure)

out << "Successfully authenticated with custom authorization"

The authorization closure receives a request builder that can be customized with headers, authentication tokens, or any other HTTP request modifications needed for your MCP server. This approach allows you to authenticate with servers that use non-OAuth authentication mechanisms such as API keys, custom tokens, or Content Server tickets.

Choosing the Right Authentication Method

When selecting an authentication method for your MCP integration, consider the following factors:

  • OAuth2: Use when the MCP server supports OAuth2 and you want automatic token management
  • Custom Authorization: Use when you need to integrate with servers using non-standard authentication or when you want more control over the authentication process

Troubleshooting

Common Issues

Authentication Errors

Problem: AuthenticationException when calling MCP methods

Solution: Verify profile configuration and ensure user has completed OAuth flow

try {
    mcp.validateProfile("mcp-profile")
} catch (AuthenticationException e) {
    def authURL = mcp.getAuthorizationUrl("${url}/runcs/${self.ID}", "mcp-profile")
    redirect authURL
}

Configuration Errors

Problem: ConfigurationException when validating profile

Solution: Check all required properties are set and property names match exactly

try {
    mcp.validateProfile("mcp-profile")
} catch (ConfigurationException e) {
    log.error("Configuration error: ${e.message}")
    out << "Please check your profile configuration: ${e.message}"
}

Tool Execution Failures

Problem: Tools fail to execute or return errors

Solution: Validate tool parameters match schema and check MCP server connectivity

try {
    def result = mcp.executeTool("tool_name", [param: "value"], "mcp-profile")
    if (result.isError) {
        log.error("Tool execution failed")
        result.content.each { content ->
            log.error("Error: ${content.text}")
        }
    }
} catch (CSMcpServiceException e) {
    log.error("Service error: ${e.message}", e)
}

Additional Resources