This guide walks through creating an addon collection that adds new MCP tools to the server.
Your addon receives two doc properties set by the MCP Server before execution:
mcpToolName -- The name of the tool being called (e.g., "blog_post_list")mcpArguments -- A JSON string containing the tool's input argumentsUse a switch statement to dispatch to the correct handler when your addon implements multiple tools.
using Contensive.BaseClasses;
using Contensive.Addons.Mcp.Shared;
using System;
using System.Collections.Generic;
namespace MyCompany.Blog {
public class BlogMcpTools : AddonBaseClass {
public override object Execute(CPBaseClass cp) {
try {
string toolName = cp.Doc.GetText("mcpToolName");
string argsJson = cp.Doc.GetText("mcpArguments");
var args = string.IsNullOrEmpty(argsJson)
? new Dictionary<string, object>()
: cp.JSON.Deserialize<Dictionary<string, object>>(argsJson)
?? new Dictionary<string, object>();
switch (toolName) {
case "blog_post_list":
return blogPostList(cp, args);
case "blog_post_create":
return blogPostCreate(cp, args);
default:
return McpResponseHelper.Error(cp, $"Unknown tool: {toolName}");
}
} catch (Exception ex) {
cp.Site.ErrorReport(ex);
return McpResponseHelper.Error(cp, "Internal error");
}
}
private string blogPostList(CPBaseClass cp, Dictionary<string, object> args) {
int pageSize = McpResponseHelper.GetIntArg(args, "pageSize", 50);
// Query and return results...
return McpResponseHelper.Success(cp, results, $"Found {results.Count} posts");
}
private string blogPostCreate(CPBaseClass cp, Dictionary<string, object> args) {
string title = McpResponseHelper.GetStringArg(args, "title");
if (string.IsNullOrEmpty(title)) {
return McpResponseHelper.Error(cp, "title is required");
}
// Create record, capture undo, return result...
return McpResponseHelper.Success(cp, new { id = post.id }, "Blog post created");
}
}
}
Your project must reference the aoMcp assembly to use McpResponseHelper and McpUndoHelper.
In your .csproj file, add a project reference or assembly reference:
<ItemGroup>
<Reference Include="aoMcp">
<HintPath>path\to\aoMcp.dll</HintPath>
</Reference>
</ItemGroup>
Add data records to your collection XML that insert rows into the "MCP Tool Definitions" content definition. Each record defines one tool.
<ImportCollection>
<CDef Name="MCP Tool Definitions">
<Data>
<Record
Content="MCP Tool Definitions"
Guid="{YOUR-UNIQUE-GUID}"
Name="blog_post_list"
>
<field Name="toolName">blog_post_list</field>
<field Name="description"><![CDATA[List blog posts with optional status filter.]]></field>
<field Name="addonGuid">{YOUR-ADDON-GUID}</field>
<field Name="category">Blog</field>
<field Name="inputSchemaJson"><![CDATA[{
"type": "object",
"properties": {
"status": {
"type": "string",
"description": "Filter by status: draft, published, archived"
},
"pageSize": {
"type": "integer",
"description": "Number of posts to return (default 50)"
}
}
}]]></field>
</Record>
</Data>
</CDef>
</ImportCollection>
The addonGuid must match the GUID of your addon defined in the same collection XML. All tool data records pointing to the same addon GUID will route to the same Execute() method, where you dispatch by mcpToolName.
Include the addon definition in your collection XML:
<Addon name="blog-mcp-tools" guid="{YOUR-ADDON-GUID}" type="add-on">
<Help>MCP tools for blog management.</Help>
<Admin>No</Admin>
<Content>No</Content>
<Template>No</Template>
<Email>No</Email>
<RemoteMethod>No</RemoteMethod>
<DotNetClass>MyCompany.Blog.BlogMcpTools</DotNetClass>
</Addon>
Note that RemoteMethod is No because the addon is not called directly via HTTP. It is invoked internally by the MCP Server via cp.Addon.Execute().