Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2189e04
Add failed message MCP server
WilliamBZA Mar 9, 2026
1c80991
Add feature flag check for MCP
WilliamBZA Mar 9, 2026
7b47a30
Update to v1.1.0 of ModelContextProtocol.AspNetCore
WilliamBZA Mar 9, 2026
5c5645e
Turn MCP off by default
WilliamBZA Mar 9, 2026
2c00d5e
Put packages in alphabetical order
WilliamBZA Mar 9, 2026
e4ab0ea
Update approvals
WilliamBZA Mar 9, 2026
4c8ecda
Don't pass the full settings object in
WilliamBZA Mar 9, 2026
add9235
Add failed message MCP server
WilliamBZA Mar 9, 2026
ae22f8a
Use /mcp as the route
WilliamBZA Mar 20, 2026
a160b4d
Add MCP for audit
WilliamBZA Mar 20, 2026
5da1181
Remove duplicate project reference
WilliamBZA Mar 20, 2026
49d946b
Update approvals
WilliamBZA Mar 20, 2026
312e2a7
Add test
WilliamBZA Mar 20, 2026
fbc8a00
Move to approvals
WilliamBZA Mar 23, 2026
481de0d
Move tests to use POCOs
WilliamBZA Mar 23, 2026
7e014b5
Move packages to be in alphabetical order
WilliamBZA Mar 23, 2026
9bf1cbc
Add unit tests for MCP
WilliamBZA Mar 23, 2026
fe4d19a
Add primary acceptance tests
WilliamBZA Mar 23, 2026
0efd5dc
Update MCP descriptions
WilliamBZA Mar 23, 2026
97b03b6
Add approval file
WilliamBZA Mar 23, 2026
a12b134
Update audit approval files
WilliamBZA Mar 23, 2026
92a4624
Update raven audit approval
WilliamBZA Mar 23, 2026
23165d4
Order approval files
WilliamBZA Mar 23, 2026
2a920c8
Add logging
WilliamBZA Mar 23, 2026
a788827
Improve MCP metadata guidance for AI clients (#5402)
danielmarbach Mar 25, 2026
06d9e45
Align wording order
danielmarbach Mar 25, 2026
5a43098
Return typed structured content for MCP tools while preserving text c…
danielmarbach Mar 26, 2026
537bc13
Add MCP source-generated JSON contexts
danielmarbach Mar 26, 2026
2959499
Removed unused options type
danielmarbach Mar 26, 2026
eb44439
Only enable destructive actions if explicitly set
WilliamBZA Apr 5, 2026
7c71b5d
Don't use backing field
WilliamBZA Apr 5, 2026
72996f4
Don't load tools from assembly
WilliamBZA Apr 5, 2026
83fb9b5
Add MCP environment data provider
johnsimons Apr 8, 2026
456b966
Forgot to register it!
johnsimons Apr 9, 2026
732568a
Merge pull request #5413 from Particular/john/data
johnsimons Apr 9, 2026
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
1 change: 1 addition & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
<PackageVersion Include="System.Reflection.MetadataLoadContext" Version="10.0.5" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="10.0.5" />
<PackageVersion Include="Validar.Fody" Version="1.9.0" />
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="1.1.0" />
<PackageVersion Include="Yarp.ReverseProxy" Version="2.3.0" />
</ItemGroup>
<ItemGroup Label="Versions to pin transitive references">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ async Task InitializeServiceControl(ScenarioContext context)
hostBuilder.AddServiceControlAuthentication(settings.OpenIdConnectSettings);
hostBuilder.AddServiceControl(settings, configuration);
hostBuilder.AddServiceControlHttps(settings.HttpsSettings);
hostBuilder.AddServiceControlApi(settings.CorsSettings);
hostBuilder.AddServiceControlApi(settings);

hostBuilder.AddServiceControlTesting(settings);

Expand All @@ -135,7 +135,7 @@ async Task InitializeServiceControl(ScenarioContext context)

host.UseTestRemoteIp();
host.UseServiceControlAuthentication(settings.OpenIdConnectSettings.Enabled);
host.UseServiceControl(settings.ForwardedHeadersSettings, settings.HttpsSettings);
host.UseServiceControl(settings.ForwardedHeadersSettings, settings.HttpsSettings, settings.EnableMcpServer);
Comment thread
danielmarbach marked this conversation as resolved.
Outdated
await host.StartAsync();
DomainEvents = host.Services.GetRequiredService<IDomainEvents>();
// Bring this back and look into the base address of the client
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
namespace ServiceControl.Audit.AcceptanceTests.Mcp;

using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using AcceptanceTesting;
using AcceptanceTesting.EndpointTemplates;
using Audit.Auditing.MessagesView;
using NServiceBus;
using NServiceBus.AcceptanceTesting;
using NServiceBus.AcceptanceTesting.Customization;
using NServiceBus.Settings;
using NUnit.Framework;

class When_mcp_server_is_enabled : AcceptanceTest
{
[SetUp]
public void EnableMcp() => SetSettings = s => s.EnableMcpServer = true;

[Test]
public async Task Should_expose_mcp_endpoint()
{
await Define<ScenarioContext>()
.Done(async _ =>
{
var response = await InitializeMcpSession();
return response.StatusCode == HttpStatusCode.OK;
})
.Run();
}

[Test]
public async Task Should_list_audit_message_tools()
{
string toolsJson = null;

await Define<ScenarioContext>()
.Done(async _ =>
{
var sessionId = await InitializeAndGetSessionId();
if (sessionId == null)
{
return false;
}

var response = await SendMcpRequest(sessionId, "tools/list", new { });
if (response == null)
{
return false;
}

toolsJson = await response.Content.ReadAsStringAsync();
return response.StatusCode == HttpStatusCode.OK;
})
.Run();

Assert.That(toolsJson, Is.Not.Null);
var doc = JsonDocument.Parse(toolsJson);
Comment thread
WilliamBZA marked this conversation as resolved.
Outdated
var result = doc.RootElement.GetProperty("result");
var tools = result.GetProperty("tools");

var toolNames = tools.EnumerateArray()
.Select(t => t.GetProperty("name").GetString())
.ToList();

Assert.That(toolNames, Does.Contain("GetAuditMessages"));
Assert.That(toolNames, Does.Contain("SearchAuditMessages"));
Assert.That(toolNames, Does.Contain("GetAuditMessagesByEndpoint"));
Assert.That(toolNames, Does.Contain("GetAuditMessagesByConversation"));
Assert.That(toolNames, Does.Contain("GetAuditMessageBody"));
Assert.That(toolNames, Does.Contain("GetKnownEndpoints"));
Assert.That(toolNames, Does.Contain("GetEndpointAuditCounts"));
}

[Test]
public async Task Should_call_get_audit_messages_tool()
{
string toolResult = null;

var context = await Define<MyContext>()
.WithEndpoint<Sender>(b => b.When((bus, c) => bus.Send(new MyMessage())))
.WithEndpoint<Receiver>()
.Done(async c =>
{
if (c.MessageId == null)
{
return false;
}

// Wait for the message to be ingested
if (!await this.TryGetMany<MessagesView>("/api/messages?include_system_messages=false&sort=id", m => m.MessageId == c.MessageId))
{
return false;
}

var sessionId = await InitializeAndGetSessionId();
if (sessionId == null)
{
return false;
}

var response = await SendMcpRequest(sessionId, "tools/call", new
{
name = "GetAuditMessages",
arguments = new { includeSystemMessages = false, page = 1, perPage = 50 }
});

if (response == null || response.StatusCode != HttpStatusCode.OK)
{
return false;
}

toolResult = await response.Content.ReadAsStringAsync();
return true;
})
.Run();

Assert.That(toolResult, Is.Not.Null);
var doc = JsonDocument.Parse(toolResult);
var result = doc.RootElement.GetProperty("result");
var content = result.GetProperty("content");
var textContent = content.EnumerateArray().First().GetProperty("text").GetString();
var messagesResult = JsonDocument.Parse(textContent);
Assert.That(messagesResult.RootElement.GetProperty("totalCount").GetInt32(), Is.GreaterThanOrEqualTo(1));
}

async Task<HttpResponseMessage> InitializeMcpSession()
{
var request = new HttpRequestMessage(HttpMethod.Post, "/mcp")
{
Content = JsonContent.Create(new
{
jsonrpc = "2.0",
id = 1,
method = "initialize",
@params = new
{
protocolVersion = "2025-03-26",
capabilities = new { },
clientInfo = new { name = "test-client", version = "1.0" }
}
})
};
return await HttpClient.SendAsync(request);
}

async Task<string> InitializeAndGetSessionId()
{
var response = await InitializeMcpSession();
if (response.StatusCode != HttpStatusCode.OK)
{
return null;
}

if (response.Headers.TryGetValues("mcp-session-id", out var values))
{
return values.FirstOrDefault();
}

return null;
}

async Task<HttpResponseMessage> SendMcpRequest(string sessionId, string method, object @params)
{
var request = new HttpRequestMessage(HttpMethod.Post, "/mcp")
{
Content = JsonContent.Create(new
{
jsonrpc = "2.0",
id = 2,
method,
@params
})
};
request.Headers.Add("mcp-session-id", sessionId);
return await HttpClient.SendAsync(request);
}

public class Sender : EndpointConfigurationBuilder
{
public Sender() =>
EndpointSetup<DefaultServerWithoutAudit>(c =>
{
var routing = c.ConfigureRouting();
routing.RouteToEndpoint(typeof(MyMessage), typeof(Receiver));
});
}

public class Receiver : EndpointConfigurationBuilder
{
public Receiver() => EndpointSetup<DefaultServerWithAudit>();

public class MyMessageHandler(MyContext testContext, IReadOnlySettings settings) : IHandleMessages<MyMessage>
{
public Task Handle(MyMessage message, IMessageHandlerContext context)
{
testContext.EndpointNameOfReceivingEndpoint = settings.EndpointName();
testContext.MessageId = context.MessageId;
return Task.CompletedTask;
}
}
}

public class MyMessage : ICommand;

public class MyContext : ScenarioContext
{
public string MessageId { get; set; }
public string EndpointNameOfReceivingEndpoint { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ async Task InitializeServiceControl(ScenarioContext context)
return criticalErrorContext.Stop(cancellationToken);
}, settings, configuration);

hostBuilder.AddServiceControlAuditApi(settings.CorsSettings);
hostBuilder.AddServiceControlAuditApi(settings);
hostBuilder.AddServiceControlHttps(settings.HttpsSettings);

hostBuilder.AddServiceControlAuditTesting(settings);
Expand All @@ -144,7 +144,7 @@ async Task InitializeServiceControl(ScenarioContext context)

host.UseTestRemoteIp();
host.UseServiceControlAuthentication(settings.OpenIdConnectSettings.Enabled);
host.UseServiceControlAudit(settings.ForwardedHeadersSettings, settings.HttpsSettings);
host.UseServiceControlAudit(settings.ForwardedHeadersSettings, settings.HttpsSettings, settings.EnableMcpServer);
await host.StartAsync();
ServiceProvider = host.Services;
InstanceTestServer = host.GetTestServer();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,6 @@
"ServiceControlQueueAddress": "Particular.ServiceControl",
"TimeToRestartAuditIngestionAfterFailure": "00:01:00",
"EnableFullTextSearchOnBodies": true,
"EnableMcpServer": false,
"ShutdownTimeout": "00:00:05"
}
Loading
Loading