Waarom ik besloot om een MCP-server te bouwen in Umbraco Mijn reis naar AI-aangedreven contentbeheer
Na jaren met Umbraco gewerkt te hebben, heb ik een goed beeld van hoe ik Umbraco slimmer kan maken. Sinds ik heb gehoord van Model Context Protocol (MCP), zag ik een kans om de kloof tussen traditioneel contentmanagement en AI-aangedreven tools te overbruggen Het idee om via AI rechtstreeks met mijn Umbraco omgeving te kunnen communiceren had direct interesse. In dit artikel neem ik je mee in mijn proces. Opmerking: In deze tutorial is er vanuit gegaan dat een Windows-machine gebruikt wordt. Sommige opdrachten kunnen afwijken als je een ander besturingssysteem gebruikt.
Timo Tielens – Jansen, Backend Developer
Wat je nodig hebt voordat we beginnen
Voordat we beginnen, moet je ervoor zorgen dat je het volgende hebt:
- Basiskennis van wat Model Context Protocol is (zo niet, dan is het de moeite waard om je hier eerst in te verdiepen)
- Ollama geïnstalleerd vanaf ollama.com
- Go 1.23 of hoger
- Enige ervaring met .NET-ontwikkeling
Umbraco up and running krijgen
Laten we beginnen met de basis. Ik begin altijd graag met een nieuwe Umbraco-installatie om conflicten met bestaande projecten te voorkomen. Installeer eerst de Umbraco-sjabloon:
Bash
dotnet new install Umbraco.Templates
Maak vervolgens een nieuw project aan:
Bash
dotnet new umbraco –name umbracoMcpServer
Navigate to the project directory and run it:
Bash
cd umbracoMcpServer
dotnet run
Wanneer je het project uitvoert, geeft de console de poort weer waarop het luistert. In mijn geval was dat https://localhost:44395. Navigeer naar deze URL in je browser en je zou het installatiescherm van Umbraco moeten zien. Ga door en voltooi de installatie. We hebben een actieve gebruiker en een volledig geconfigureerde Umbraco-instantie nodig.
Zodra je bent ingelogd in de back-end, kan je de server stoppen met Ctrl + C.
De MCP-magie toevoegen
Nu komt het interessante gedeelte. We moeten de Model Context Protocol SDK toevoegen. Op het moment van schrijven is dit nog in preview (versie 0.3.0-preview. 1), wat betekent dat er nog dingen kunnen veranderen, maar dat hoort bij het plezier van werken met geavanceerde technologie.
Bash
dotnet add package ModelContextProtocol –prerelease
dotnet add package ModelContextProtocol.AspNetCore –prerelease
Ik moest ook het .NET Hosting-pakket toevoegen:
Bash
dotnet add package Microsoft.Extensions.Hosting
Organisatie van onze MCP-componenten
Ik houd ervan om dingen georganiseerd te houden, dus laten we een goede mapstructuur aanmaken voor onze MCP-implementatie:
Bash
mkdir mcp
cd mcp
mkdir tools
mkdir models
Deze structuur helpt ons om onze aandachtspunten te scheiden en de code beter onderhoudbaar te maken naarmate we meer functies toevoegen.
Het “Data Transfer Object” maken
Nu we onze mapstructuur hebben, gaan we onze eerste component maken. We beginnen met een Data Transfer Object (DTO) dat ons helpt de lidgegevens voor onze MCP-server vorm te geven.
Maak een nieuw bestand met de naam MemberMcpDto.cs in de map mcp/models:
Csharp
using System.Diagnostics.CodeAnalysis;
using Umbraco.Cms.Core.Models;
namespace umbracoMcpServer.mcp.models;
public class MemberMcpDto
{
public int Identifier { get; set; }
public string? Name { get; set; }
public required string Username { get; set; }
public required string Email { get; set; }
public DateTime? CreateDate { get; set; }
public DateTime? UpdateDate { get; set; }
public bool IsApproved { get; set; }
public bool IsLockedOut { get; set; }
public required string MemberTypeAlias { get; set; }
[SetsRequiredMembers]
public MemberMcpDto(IMember member)
{
Identifier = member.Id;
Name = member.Name;
Username = member.Username;
Email = member.Email;
CreateDate = member.CreateDate;
UpdateDate = member.UpdateDate;
IsApproved = member.IsApproved;
IsLockedOut = member.IsLockedOut;
MemberTypeAlias = member.ContentType.Alias;
}
}
De MCP-tool bouwen
Nu wordt het echt interessant. De MCP-tool is wat AI-Agents daadwerkelijk van functionaliteit voorziet. Maak een nieuw bestand met de naam MemberTool.cs in de map mcp/tools:
Csharp
using System.ComponentModel;
using System.Text.Json;
using ModelContextProtocol.Server;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Models;
using umbracoMcpServer.mcp.models;
namespace umbracoMcpServer.mcp.tools;
[McpServerToolType]
public sealed class MemberTool
{
private static readonly JsonSerializerOptions SerializerOptions = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
[McpServerTool, Description(“Get all members from the Umbraco database. Returns a list of members in JSON format.”)]
public Task<string> GetMembers(IMemberService memberService)
{
try
{
var members = memberService.GetAllMembers();
var enumerable = members.ToList();
if (enumerable.Count == 0)
return Task.FromResult(“{}”);
var membersList = enumerable.Select(member => new MemberMcpDto(member)).ToList();
var json = JsonSerializer.Serialize(membersList, SerializerOptions);
return Task.FromResult(json);
}
catch
{
// Return empty JSON object on error
return Task.FromResult(“{}”);
}
}
[McpServerTool, Description(“Create a new member in the Umbraco database. Returns the created member details in JSON format.”)]
public Task<string> CreateMember(
IMemberService memberService,
[Description(“The username for the new member. This will be used for login purposes.”)]
string username,
[Description(“The email address for the new member. Must be a valid email format.”)]
string email,
[Description(“The display name for the new member. This is typically the full name or display name.”)]
string name,
[Description(“The password for the new member. If empty, the member will be created without a password.”)]
string password = “”)
{
try
{
var member = memberService.CreateMember(username, email, name, “Member”);
if (!string.IsNullOrEmpty(password))
member.RawPasswordValue = password;
memberService.Save(member);
var json = JsonSerializer.Serialize(new MemberMcpDto(member), SerializerOptions);
return Task.FromResult(json);
}
catch (Exception ex)
{
var errorResponse = new
{
success = false,
error = ex.Message
};
var errorJson = JsonSerializer.Serialize(errorResponse, SerializerOptions);
return Task.FromResult(errorJson);
}
}
}
Wat ik zo geweldig vind aan deze aanpak, is hoe het MCP-framework gebruikmaakt van dependency injection om de IMemberService automatisch te leveren. De Description-attributen zijn cruciaal: ze helpen AI-Agents te begrijpen wat elke tool doet en hoe de parameters correct moeten worden gebruikt.
Alles met elkaar verbinden
Nu moeten we ons bestand Program.cs bijwerken om onze MCP-server en tools te registreren. “This is where the magic happens”:
Csharp
using ModelContextProtocol.AspNetCore;
using umbracoMcpServer.mcp.tools;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMcpServer()
.WithHttpTransport()
.WithTools<MemberTool>();
builder.CreateUmbracoBuilder()
.AddBackOffice()
.AddWebsite()
.AddComposers()
.Build();
var app = builder.Build();
app.MapMcp(“/mcp”);
await app.BootUmbracoAsync();
app.UseUmbraco()
.WithMiddleware(u =>
{
u.UseBackOffice();
u.UseWebsite();
})
.WithEndpoints(u =>
{
u.UseBackOfficeEndpoints();
u.UseWebsiteEndpoints();
});
await app.RunAsync();
De belangrijkste regel hier is app.MapMcp(“/mcp”), die onze MCP-server verbindt aan het endpoint /mcp.
Onze MCP-server testen
Start de applicatie en ga naar https://localhost:44395/mcp. U zou een foutmelding ‘Sessie niet gevonden’ moeten zien, wat eigenlijk betekent dat alles correct werkt! De MCP-server is actief en wacht op een geschikte MCP-client om verbinding te maken.
Dingen om in gedachten te houden
Aangezien dit een eenvoudige implementatie is, zijn er een paar dingen waar je rekening mee moet houden:
- Beveiliging: Deze implementatie bevat geen authenticatie of autorisatie. In een productieomgeving moet u uw MCP-eindpunten beveiligen om ongeoorloofde toegang te voorkomen.
- Foutafhandeling: De foutafhandeling in de methode CreateMember is eenvoudig. U kunt deze verbeteren om specifieke uitzonderingen of validatiefouten beter af te handelen.
- Opmerkingen en documentatie: Hoewel ik enkele opmerkingen heb toegevoegd, wilt u deze wellicht uitbreiden om de code begrijpelijker te maken voor anderen (of voor uzelf in de toekomst).
- Testen: Dit voorbeeld bevat geen unit-tests of integratietests. Overweeg tests toe te voegen om de betrouwbaarheid van uw MCP-server te waarborgen.
- Logboekregistratie: Het implementeren van logboekregistratie is nuttig voor het opsporen van fouten en het bewaken van de activiteit van de MCP-server.
- Annuleringstokens: Overweeg voor langdurige bewerkingen het gebruik van annuleringstokens om een soepele afsluiting of annulering mogelijk te maken. Meer informatie over annuleringstokens in .NET.
- Scheiding Het is een goed idee om de AI-aanvraag en de afhandeling in verschillende klassen te scheiden. Dit maakt het gemakkelijker om in de toekomst meer tools toe te voegen.
- Gegevensvalidatie: De methode CreateMember valideert de invoergegevens niet. U kunt controles toevoegen om te controleren of de gebruikersnaam en het e-mailadres geldig zijn voordat u een lid aanmaakt.
De LLM verbinden met onze MCP-server
Nu komt het spannende gedeelte: onze MCP-server daadwerkelijk gebruiken! Om een LLM zoals Ollama te verbinden met onze MCP-server, ontdekte ik MCPHost van Mark3Labs. Deze tool maakt het verbindingsproces verrassend eenvoudig. Installeer eerst MCPHost:
Bash
go install github.com/mark3labs/mcphost@latest
Configuratie
Maak een nieuw bestand met de naam mcphost.yaml in de hoofdmap van uw project:
Json
{
“mcpServers”: {
“umbraco”: {
“type”: “remote”,
“url”: “https://localhost:44395/mcp”
}
}
}
Ik heb ervoor gekozen om dit te testen met het model qwen2.5:1.5b, maar je kunt elk model gebruiken dat tool calling ondersteunt. Compatibele modellen vind je op de Ollama-website. Start de MCP-host met:
Bash
mcphost -m ollama:qwen2.5:1.5b –config “what/ever/your/path/is/umbracoMcpServer/mcphost.yaml”
Het in actie zien
Het moment van de waarheid! Ik vroeg de AI om een nieuw lid aan te maken:
“Create a new member for me. The username and name are Timo, the email is development@timotielens.nl and the password is something secure.”
En het werkte! De AI begreep mijn verzoek, riep de CreateMember-tool op met de juiste parameters en creëerde met succes een nieuw lid in Umbraco. Toen ik de backoffice van Umbraco controleerde, stond daar het nieuwe lid, compleet met alle details die ik had opgegeven.

Ik zou ook kunnen vragen om alle leden weer te geven, waarna een mooi opgemaakte JSON-respons met alle ledengegevens zou worden teruggestuurd. De AI-assistent heeft nu rechtstreeks toegang tot mijn Umbraco-gegevens en kan daarop bewerkingen uitvoeren. Het is alsof ik een conversatie-interface voor mijn CMS heb.
Final Thoughts
Het bouwen van een MCP-server in Umbraco was een van die projecten die aanvoelen als een blik in de toekomst. Het gaat niet alleen om het toevoegen van AI-functies aan een CMS, maar om een fundamentele verandering in de manier waarop we omgaan met onze contentmanagementsystemen.
Het idee dat ik nu een gesprek kan voeren met mijn Umbraco-instantie en deze kan vragen om leden aan te maken, gegevens op te vragen of mogelijk content te beheren, voelt bijna magisch. En dit is nog maar het begin. Met deze basis kan ik eenvoudig tools toevoegen voor contentbeheer, mediabeheer of zelfs complexe workflowautomatisering.
Wat me het meest enthousiast maakt, is hoe dit de kloof tussen technische en niet-technische gebruikers overbrugt. Stel je voor dat contentredacteuren een AI-assistent kunnen vragen om “een nieuwe blogpost over onze nieuwste productlancering te maken” en dat deze dat ook daadwerkelijk doet, compleet met de juiste categorisering en metadata.
Als je nieuwsgierig bent naar MCP en meer wilt weten, raad ik je aan de officiële MCP-documentatie te raadplegen. Het protocol is nog in ontwikkeling, maar het potentieel is enorm. We staan nog maar aan het begin van wat er mogelijk is als we AI-Agents directe toegang geven tot onze systemen en gegevens.