Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CrestApps.OrchardCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrestApps.OrchardCore.AI.Mc
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrestApps.OrchardCore.AI.Mcp.Core", "src\Core\CrestApps.OrchardCore.AI.Mcp.Core\CrestApps.OrchardCore.AI.Mcp.Core.csproj", "{A49E8890-01E5-4891-8420-6DDD2E51C178}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrestApps.OrchardCore.AI.Mcp.Server", "src\Modules\CrestApps.OrchardCore.AI.Mcp.Server\CrestApps.OrchardCore.AI.Mcp.Server.csproj", "{BE501DB8-7EC8-6887-5D3E-0BBFB2CDA034}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -189,6 +191,10 @@ Global
{A49E8890-01E5-4891-8420-6DDD2E51C178}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A49E8890-01E5-4891-8420-6DDD2E51C178}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A49E8890-01E5-4891-8420-6DDD2E51C178}.Release|Any CPU.Build.0 = Release|Any CPU
{BE501DB8-7EC8-6887-5D3E-0BBFB2CDA034}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BE501DB8-7EC8-6887-5D3E-0BBFB2CDA034}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BE501DB8-7EC8-6887-5D3E-0BBFB2CDA034}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BE501DB8-7EC8-6887-5D3E-0BBFB2CDA034}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -220,6 +226,7 @@ Global
{8BB36191-F041-43ED-B6FA-1C6302ED7F5B} = {EC2B9CAB-56C8-4421-ACA2-43C09B71DAB5}
{3ED9AAF7-BCFB-4456-A2FB-1A290C5C6240} = {C8D22F16-3D2B-4053-B0D5-A1F5EA5A91C2}
{A49E8890-01E5-4891-8420-6DDD2E51C178} = {EC2B9CAB-56C8-4421-ACA2-43C09B71DAB5}
{BE501DB8-7EC8-6887-5D3E-0BBFB2CDA034} = {C8D22F16-3D2B-4053-B0D5-A1F5EA5A91C2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0E9F9B00-7078-4EA8-87A9-0B2E74375F1E}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<RootNamespace>$(MSBuildProjectName)</RootNamespace>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<Title>CrestApps OrchardCore AI MCP Server Module</Title>
<Description>
$(CrestAppsDescription)

Provides the app with a Model Context Protocol (MCP) server.
</Description>
<PackageTags>$(PackageTags) OrchardCoreCMS AI MCP ModelContextProtocol</PackageTags>
</PropertyGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="OrchardCore.Module.Targets" />
<PackageReference Include="OrchardCore.ContentManagement" />
<PackageReference Include="OrchardCore.ContentTypes.Abstractions" />
<PackageReference Include="OrchardCore.DisplayManagement" />
<PackageReference Include="ModelContextProtocol.AspNetCore" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Abstractions\CrestApps.OrchardCore.Abstractions\CrestApps.OrchardCore.Abstractions.csproj" />
<ProjectReference Include="..\..\Core\CrestApps.OrchardCore.AI.Core\CrestApps.OrchardCore.AI.Core.csproj" />
<ProjectReference Include="..\..\Core\CrestApps.OrchardCore.AI.Mcp.Core\CrestApps.OrchardCore.AI.Mcp.Core.csproj" />
</ItemGroup>

<ItemGroup>
<!--
Ensure this module directly depends on the required packages so that when someone installs it,
they won't need to manually include the AI and SignalR packages.
This guarantees the module functions correctly and resolves the feature dependency.
-->
<ProjectReference Include="..\CrestApps.OrchardCore.AI\CrestApps.OrchardCore.AI.csproj" PrivateAssets="none" />
<ProjectReference Include="..\CrestApps.OrchardCore.AI.Mcp\CrestApps.OrchardCore.AI.Mcp.csproj" PrivateAssets="none" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using CrestApps.OrchardCore.AI.Mcp.Core.Models;
using CrestApps.OrchardCore.AI.Mcp.Server.ViewModels;
using CrestApps.OrchardCore.AI.Models;
using Microsoft.Extensions.Localization;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Entities;

namespace CrestApps.OrchardCore.AI.Mcp.Server.Drivers;

internal sealed class AIProfileMcpServerDisplayDriver : DisplayDriver<AIProfile>
{
internal readonly IStringLocalizer S;

public AIProfileMcpServerDisplayDriver(
IStringLocalizer<AIProfileMcpServerDisplayDriver> stringLocalizer)
{
S = stringLocalizer;
}

public override IDisplayResult Edit(AIProfile profile, BuildEditorContext context)
{
return Initialize<McpServerMetadataViewModel>("EditProfileMcpServer_Edit", model =>
{
var mcpMetadata = profile.As<McpServerMetadata>();

model.UseLocalServer = mcpMetadata.UseLocalServer;

}).Location("Content:8.1");
}

public override async Task<IDisplayResult> UpdateAsync(AIProfile profile, UpdateEditorContext context)
{
var model = new McpServerMetadataViewModel();

await context.Updater.TryUpdateModelAsync(model, Prefix);

profile.Alter<McpServerMetadata>(part =>
{
part.UseLocalServer = model.UseLocalServer;
});

return Edit(profile, context);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using CrestApps.OrchardCore.AI.Mcp.Core.Models;
using CrestApps.OrchardCore.AI.Models;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Transport;
using OrchardCore.Entities;

namespace CrestApps.OrchardCore.AI.Mcp.Server.Handlers;

public sealed class McpServerToolsAICompletionServiceHandler : IAICompletionServiceHandler
{
public async Task ConfigureAsync(CompletionServiceConfigureContext context)
{
if (!context.IsFunctionInvocationSupported)
{
return;
}

var mcpMetadata = context.Profile.As<McpServerMetadata>();

if (mcpMetadata.UseLocalServer)
{
return;
}

context.ChatOptions.Tools ??= [];

var inputStream = new MemoryStream();
var outputStream = new MemoryStream();

// TODO, create in-memory transport.
var transport = new StreamClientTransport(inputStream, outputStream);

var client = await McpClientFactory.CreateAsync(transport);

foreach (var tool in await client.ListToolsAsync())
{
context.ChatOptions.Tools.Add(tool);
}
}
}
15 changes: 15 additions & 0 deletions src/Modules/CrestApps.OrchardCore.AI.Mcp.Server/Manifest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using CrestApps.OrchardCore.AI.Core;
using OrchardCore.Modules.Manifest;

[assembly: Module(
Name = "Model Context Protocol (MCP) Server",
Author = CrestAppsManifestConstants.Author,
Website = CrestAppsManifestConstants.Website,
Version = CrestAppsManifestConstants.Version,
Description = "Provides a way to enable Context Protocol (MCP) Servers on your site.",
Category = "Artificial Intelligence",
Dependencies =
[
AIConstants.Feature.Area,
]
)]
46 changes: 46 additions & 0 deletions src/Modules/CrestApps.OrchardCore.AI.Mcp.Server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Model Context Protocol (MCP) Server

The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol that enables seamless integration between LLM applications and external data sources and tools. Whether you're building an AI-powered IDE, enhancing a chat interface, or creating custom AI workflows, MCP provides a standardized way to connect LLMs with the context they need.

## Defining MCP Capabilities

The **Model Context Protocol (MCP) Server** feature allows you to expose an MCP server that can be accessed by other MCP clients. These clients can interact with your server and take advantage of all the capabilities you define—making it easy to share, reuse, and integrate powerful AI-driven tools across distributed systems.

We use the official [MCP C# SDK](https://github.com/modelcontextprotocol/csharp-sdk) to enable and configure the MCP server. For detailed guidance on defining server capabilities, MCP prompts, and MCP resources, please refer to the [MCP C# SDK documentation](https://github.com/modelcontextprotocol/csharp-sdk).

Below is a simple example of how to create an MCP capability that echoes a message back to the client.

### Step 1: Install the Required Package

Install the `CrestApps.OrchardCore.AI.Mcp.Core` NuGet package in the module where you want to define the server capabilities.

### Step 2: Define the Capability

Create a tool class and decorate it with the appropriate attributes to define the MCP capability:

```csharp
[McpServerToolType]
public class EchoTool
{
[McpServerTool(Name = "echo"), Description("Echoes the message back to the client.")]
public static string Echo(string message) => $"Echo: {message}";
}
```

### Step 3: Register the Capability in `Startup.cs`

Finally, register the tool in your module's `Startup` class:

```csharp
public sealed class Startup : StartupBase
{
public override void ConfigureServices(IServiceCollection services)
{
services
.AddMcpServer()
.WithTools<EchoTool>();
}
}
```

This setup enables your MCP server to expose the `echo` capability, allowing any compatible MCP client to call it and receive a response.
27 changes: 27 additions & 0 deletions src/Modules/CrestApps.OrchardCore.AI.Mcp.Server/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using CrestApps.OrchardCore.AI.Mcp.Server.Drivers;
using CrestApps.OrchardCore.AI.Mcp.Server.Handlers;
using CrestApps.OrchardCore.AI.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.Modules;

namespace CrestApps.OrchardCore.AI.Mcp.Server;

public sealed class Startup : StartupBase
{
public override void ConfigureServices(IServiceCollection services)
{
services
.AddMcpServer();

services.AddDisplayDriver<AIProfile, AIProfileMcpServerDisplayDriver>();
services.AddScoped<IAICompletionServiceHandler, McpServerToolsAICompletionServiceHandler>();
}

public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
{
routes.MapMcp();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace CrestApps.OrchardCore.AI.Mcp.Server.ViewModels;

public class McpServerMetadataViewModel
{
public bool UseLocalServer { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@using CrestApps.OrchardCore.AI.Mcp.Server.ViewModels
@using OrchardCore

@model McpServerMetadataViewModel

<h5>@T["Local MCP Server"]</h5>

<div class="@Orchard.GetWrapperClasses()">
<div class="@Orchard.GetEndClasses(true)">
<div class="form-check">
<input type="checkbox" class="form-check-input" asp-for="UseLocalServer">
<label class="form-check-label" asp-for="UseLocalServer">@T["Use local MCP server"]</label>
<span class="hint dashed">@T["When enabled, this profile will have access to all tools provided by the local MCP server."]</span>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@inherits OrchardCore.DisplayManagement.Razor.RazorPage<TModel>

@using OrchardCore.DisplayManagement
@using OrchardCore.DisplayManagement.Views

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, OrchardCore.DisplayManagement
@addTagHelper *, OrchardCore.ResourceManagement
@addTagHelper *, OrchardCore.Contents.TagHelpers
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Modules\CrestApps.OrchardCore.AI.Mcp.Server\CrestApps.OrchardCore.AI.Mcp.Server.csproj" />
<ProjectReference Include="..\..\Modules\CrestApps.OrchardCore.AI.Mcp\CrestApps.OrchardCore.AI.Mcp.csproj" />
<ProjectReference Include="..\..\Targets\CrestApps.OrchardCore.Cms.Core.Targets\CrestApps.OrchardCore.Cms.Core.Targets.csproj" />
</ItemGroup>
Expand Down