diff --git a/AzureSignalR.sln b/AzureSignalR.sln
index bc87fa030..21f7d7cfd 100644
--- a/AzureSignalR.sln
+++ b/AzureSignalR.sln
@@ -86,6 +86,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagementPublisher", "samp
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatSample.RazorPages", "samples\ChatSample\ChatSample.RazorPages\ChatSample.RazorPages.csproj", "{D7A38BB7-6416-4E15-AD87-D525F203F549}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatMcp", "samples\ChatSample\ChatMcp\ChatMcp.csproj", "{DCF069DE-1C30-EE2B-D0ED-3355E998847B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -196,6 +198,10 @@ Global
{D7A38BB7-6416-4E15-AD87-D525F203F549}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7A38BB7-6416-4E15-AD87-D525F203F549}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7A38BB7-6416-4E15-AD87-D525F203F549}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DCF069DE-1C30-EE2B-D0ED-3355E998847B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DCF069DE-1C30-EE2B-D0ED-3355E998847B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DCF069DE-1C30-EE2B-D0ED-3355E998847B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DCF069DE-1C30-EE2B-D0ED-3355E998847B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -229,6 +235,7 @@ Global
{82C1FF3D-EC6C-4B21-B6A4-E69E8D75D0D0} = {2429FBD8-1FCE-4C42-AA28-DF32F7249E77}
{0F32E624-7AC8-4CA7-8ED9-E1A877442020} = {C965ED06-6A17-4329-B3C6-811830F4F4ED}
{D7A38BB7-6416-4E15-AD87-D525F203F549} = {C965ED06-6A17-4329-B3C6-811830F4F4ED}
+ {DCF069DE-1C30-EE2B-D0ED-3355E998847B} = {C965ED06-6A17-4329-B3C6-811830F4F4ED}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7945A4E4-ACDB-4F6E-95CA-6AC6E7C2CD59}
diff --git a/samples/ChatSample/ChatMcp/ChatMcp.csproj b/samples/ChatSample/ChatMcp/ChatMcp.csproj
new file mode 100644
index 000000000..238cdc2a8
--- /dev/null
+++ b/samples/ChatSample/ChatMcp/ChatMcp.csproj
@@ -0,0 +1,20 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ChatSample/ChatMcp/ChatProxy.cs b/samples/ChatSample/ChatMcp/ChatProxy.cs
new file mode 100644
index 000000000..dd7689df6
--- /dev/null
+++ b/samples/ChatSample/ChatMcp/ChatProxy.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Threading.Tasks;
+
+using ChatSample;
+using Microsoft.AspNetCore.SignalR.Client;
+
+namespace ChatMcp;
+
+public sealed class ChatProxy(HubConnection connection) : IChatHub
+{
+ public async Task BroadcastMessage(string name, string message)
+ {
+ await connection.InvokeAsync(nameof(IChatHub.BroadcastMessage), name, message);
+ }
+
+ Task IChatHub.Echo(string name, string message)
+ {
+ // we dont need this ability in the MCP tool.
+ throw new NotSupportedException();
+ }
+}
diff --git a/samples/ChatSample/ChatMcp/ChatTool.cs b/samples/ChatSample/ChatMcp/ChatTool.cs
new file mode 100644
index 000000000..4968f9d34
--- /dev/null
+++ b/samples/ChatSample/ChatMcp/ChatTool.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.ComponentModel;
+using System.Threading.Tasks;
+
+using ModelContextProtocol.Server;
+
+namespace ChatMcp;
+
+[McpServerToolType]
+public class ChatTool(ChatProxy proxy)
+{
+ [McpServerTool]
+ [Description("Broadcast a message to all clients.")]
+ public async Task BroadcastMessage(
+ [Description("The text message.")]
+ string message)
+ {
+ await proxy.BroadcastMessage("MCP", message);
+ }
+}
diff --git a/samples/ChatSample/ChatMcp/Program.cs b/samples/ChatSample/ChatMcp/Program.cs
new file mode 100644
index 000000000..96045c920
--- /dev/null
+++ b/samples/ChatSample/ChatMcp/Program.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+using System;
+
+using ChatMcp;
+using Microsoft.AspNetCore.SignalR.Client;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+var url = Environment.GetEnvironmentVariable("ServerUrl");
+if (string.IsNullOrEmpty(url))
+{
+ url = "http://localhost:5050/chat";
+}
+if (!Uri.TryCreate(url, UriKind.Absolute, out var serverUrl))
+{
+ Console.WriteLine($"Invalid ServerUrl: {url}");
+ return;
+}
+
+await using var connection = new HubConnectionBuilder()
+ .WithUrl(url)
+ .WithAutomaticReconnect()
+ .Build();
+await connection.StartAsync();
+
+var builder = Host.CreateApplicationBuilder(args);
+builder.Logging.AddConsole(consoleLogOptions =>
+{
+ // Configure all logs to go to stderr
+ consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
+});
+
+builder.Services
+ .AddSingleton(new ChatProxy(connection))
+ .AddMcpServer()
+ .WithStdioServerTransport()
+ .WithToolsFromAssembly();
+await builder.Build().RunAsync();
diff --git a/samples/ChatSample/ChatSample/ChatHub.cs b/samples/ChatSample/ChatSample/ChatHub.cs
index 042036873..d5143d165 100644
--- a/samples/ChatSample/ChatSample/ChatHub.cs
+++ b/samples/ChatSample/ChatSample/ChatHub.cs
@@ -10,17 +10,17 @@
namespace ChatSample;
-public class ChatHub(IHubContext context) : Hub
+public class ChatHub(IHubContext context) : Hub, IChatHub
{
- public void BroadcastMessage(string name, string message)
+ public async Task BroadcastMessage(string name, string message)
{
- Clients.All.SendAsync("broadcastMessage", name, message);
+ await Clients.All.SendAsync("broadcastMessage", name, message);
Console.WriteLine("Broadcasting...");
}
- public void Echo(string name, string message)
+ public async Task Echo(string name, string message)
{
- Clients.Caller.SendAsync("echo", name,
+ await Clients.Caller.SendAsync("echo", name,
$"{message} (echo from server, Client IP: {Context.GetHttpContext().Connection.RemoteIpAddress}");
Console.WriteLine("Echo...");
}
diff --git a/samples/ChatSample/ChatSample/IChatHub.cs b/samples/ChatSample/ChatSample/IChatHub.cs
new file mode 100644
index 000000000..4c520d6d8
--- /dev/null
+++ b/samples/ChatSample/ChatSample/IChatHub.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Threading.Tasks;
+
+namespace ChatSample;
+
+public interface IChatHub
+{
+ Task BroadcastMessage(string name, string message);
+ Task Echo(string name, string message);
+}
diff --git a/samples/ChatSample/ChatSample/Startup.cs b/samples/ChatSample/ChatSample/Startup.cs
index 2050bc0ad..b4e3b70b2 100644
--- a/samples/ChatSample/ChatSample/Startup.cs
+++ b/samples/ChatSample/ChatSample/Startup.cs
@@ -45,6 +45,8 @@ private enum AuthTypes
{
VisualStudio = 0,
+ VisualStudioCode,
+
ApplicationWithCertificate,
ApplicationWithClientSecret,
@@ -66,7 +68,8 @@ public void ConfigureServices(IServiceCollection services)
{
TokenCredential credential = AuthType switch
{
- AuthTypes.VisualStudio => new VisualStudioCodeCredential(),
+ AuthTypes.VisualStudio => new VisualStudioCredential(),
+ AuthTypes.VisualStudioCode => new VisualStudioCodeCredential(),
AuthTypes.ApplicationWithCertificate => new ClientCertificateCredential(TenantId, AppClientId, "path-to-cert-file"),
AuthTypes.ApplicationWithClientSecret => new ClientSecretCredential(TenantId, AppClientId, "client-secret-value"),
AuthTypes.ApplicationWithFederatedIdentity => GetClientAssertionCredential(TenantId, AppClientId, MsiClientId),