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:
-
Extend AI capabilities: Leverage external tools and services through MCP servers, expanding the functionality available to AI models.
-
Standardize tool integration: Use a consistent protocol for connecting AI models with external resources, simplifying integration complexity.
-
Enable dynamic tool discovery: Automatically discover and utilize tools available on MCP servers without hardcoding integrations.
-
Enhance AI workflows: Integrate MCP tools with OpenAI function calling, allowing AI models to automatically execute external tools based on user requests.
-
Maintain security: Implement secure authentication mechanisms (OAuth2 or custom) to protect access to external tools and resources.
-
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
scriptorremote. 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 withoutdef. A closure name matches[A-Za-z_][A-Za-z0-9_]*, andstartis reserved for the behaviour. - One argument field. The closure receives one
Mapwith the arguments, decoded from the JSON form fieldmcpArguments. 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
ListofMapwithnodeId,name, and an optionalmimeType. - 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
Stringstays text.nullis empty text, and numbers and booleans become their text. - A
Listbecomes JSON text. - A
Mapbecomes JSON text plusstructuredContent. - 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 withnodeId,name, and an optionalmimeType. 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:
- OAuth2 Authentication (automatic): Full OAuth2 flow with automatic token management
- 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
validateProfilemethod ensures the MCP profile is properly configured and authenticated. -
Tool Execution: The
executeToolmethod takes the tool name, parameters, and profile ID to execute the tool on the MCP server. -
Result Processing: The method returns a
CSMcpCallToolResultobject 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
listToolsAsOpenAIResponseToolsmethod 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:
-
Dynamic Tool Discovery: AI models can automatically discover and use new tools as they become available on MCP servers.
-
Standardized Interface: MCP provides a standardized way to expose tools, making integration consistent across different services.
-
Flexible Execution: Tools can be executed automatically based on AI decisions, reducing the need for manual intervention.
-
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:
- OAuth2 Authentication (automatic): Requires full OAuth configuration
- 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)
}