From dfb6726fa161d2384b806162312280562a8405ec Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Tue, 28 Apr 2026 11:41:11 -0700 Subject: [PATCH 1/2] [release/10.0] Add the mcpserver project template to the .NET 10 SDK Cherry-pick of #66260 into release/10.0, updating: - The template group identity precedence to 10000 - The template's target framework to net10.0 - The template package's name suffix to have 10.0 - The template test script to use the 10.0 name suffix and version --- AspNetCore.slnx | 1 + eng/Build.props | 1 + eng/Dependencies.props | 2 + eng/Versions.props | 2 + .../McpServer.ProjectTemplates/.gitignore | 3 + .../McpServer-Local-CSharp.csproj.in | 52 ++++ .../McpServer-Remote-CSharp.csproj.in | 35 +++ ...icrosoft.McpServer.ProjectTemplates.csproj | 24 ++ .../content/Directory.Build.props | 8 + .../content/Directory.Build.targets | 7 + .../.template.config/dotnetcli.host.json | 33 +++ .../.template.config/ide.host.json | 19 ++ .../.template.config/ide/icon.ico | Bin 0 -> 38045 bytes .../.template.config/template.json | 223 ++++++++++++++++++ .../common/Tools/RandomNumberTools.cs | 18 ++ .../McpServer-CSharp/local/.mcp/server.json | 22 ++ .../content/McpServer-CSharp/local/Program.cs | 16 ++ .../content/McpServer-CSharp/local/README.md | 104 ++++++++ .../remote/McpServer-CSharp.http | 21 ++ .../McpServer-CSharp/remote/Program.cs | 21 ++ .../remote/Properties/launchSettings.json | 23 ++ .../content/McpServer-CSharp/remote/README.md | 76 ++++++ src/ProjectTemplates/ProjectTemplates.slnf | 1 + src/ProjectTemplates/README.md | 1 + .../Shared/TemplatePackageInstaller.cs | 3 +- .../scripts/Run-McpServer-Locally.ps1 | 12 + .../Templates.Tests/McpServerTemplateTest.cs | 143 +++++++++++ .../Templates.Tests/Templates.Tests.csproj | 5 + .../Templates.Tests/template-baselines.json | 22 ++ 29 files changed, 897 insertions(+), 1 deletion(-) create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/.gitignore create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/McpServer-Local-CSharp.csproj.in create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/McpServer-Remote-CSharp.csproj.in create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/Microsoft.McpServer.ProjectTemplates.csproj create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/content/Directory.Build.props create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/content/Directory.Build.targets create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/.template.config/dotnetcli.host.json create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/.template.config/ide.host.json create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/.template.config/ide/icon.ico create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/.template.config/template.json create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/common/Tools/RandomNumberTools.cs create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/local/.mcp/server.json create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/local/Program.cs create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/local/README.md create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/remote/McpServer-CSharp.http create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/remote/Program.cs create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/remote/Properties/launchSettings.json create mode 100644 src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/remote/README.md create mode 100644 src/ProjectTemplates/scripts/Run-McpServer-Locally.ps1 create mode 100644 src/ProjectTemplates/test/Templates.Tests/McpServerTemplateTest.cs diff --git a/AspNetCore.slnx b/AspNetCore.slnx index 6e083fe668c3..9c92ed9fccd8 100644 --- a/AspNetCore.slnx +++ b/AspNetCore.slnx @@ -771,6 +771,7 @@ + diff --git a/eng/Build.props b/eng/Build.props index 985feb8a8ec8..8c65e2527e6e 100644 --- a/eng/Build.props +++ b/eng/Build.props @@ -45,6 +45,7 @@ $(RepoRoot)src\Components\Web.JS\node_modules\**\*.*proj; $(RepoRoot)src\Installers\**\*.*proj; $(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.*proj; + $(RepoRoot)src\ProjectTemplates\McpServer.ProjectTemplates\content\**\*.*proj; $(RepoRoot)src\SignalR\clients\ts\**\node_modules\**\*.*proj; " /> diff --git a/eng/Dependencies.props b/eng/Dependencies.props index aa3a4b83dcb1..9047b83988c6 100644 --- a/eng/Dependencies.props +++ b/eng/Dependencies.props @@ -204,6 +204,8 @@ may be turned into `` items in projects. + + diff --git a/eng/Versions.props b/eng/Versions.props index 730f66cfaf5d..73b578dc8e44 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -150,6 +150,8 @@ 3.14.1 0.3.46-beta $(MessagePackVersion) + 1.2.0 + $(ModelContextProtocolVersion) 4.10.0 0.11.2 2.2.1 diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/.gitignore b/src/ProjectTemplates/McpServer.ProjectTemplates/.gitignore new file mode 100644 index 000000000000..9310b1ea5295 --- /dev/null +++ b/src/ProjectTemplates/McpServer.ProjectTemplates/.gitignore @@ -0,0 +1,3 @@ +# This file is generated by the build +content/*/*.*proj +content/*/*/*.*proj diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/McpServer-Local-CSharp.csproj.in b/src/ProjectTemplates/McpServer.ProjectTemplates/McpServer-Local-CSharp.csproj.in new file mode 100644 index 000000000000..e082eb4fe7a2 --- /dev/null +++ b/src/ProjectTemplates/McpServer.ProjectTemplates/McpServer-Local-CSharp.csproj.in @@ -0,0 +1,52 @@ + + + + ${DefaultNetCoreTargetFramework} + + win-x64;win-arm64;osx-arm64;linux-x64;linux-arm64;linux-musl-x64 + + Major + + Exe + enable + enable + + + true + McpServer + + + + true + true + + + true + + + + + true + true + + + + README.md + SampleMcpServer + 0.1.0-beta + AI; MCP; server; stdio + An MCP server using the MCP C# SDK. + + + + + + + + + + + + + + diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/McpServer-Remote-CSharp.csproj.in b/src/ProjectTemplates/McpServer.ProjectTemplates/McpServer-Remote-CSharp.csproj.in new file mode 100644 index 000000000000..bfc1b385b16f --- /dev/null +++ b/src/ProjectTemplates/McpServer.ProjectTemplates/McpServer-Remote-CSharp.csproj.in @@ -0,0 +1,35 @@ + + + + ${DefaultNetCoreTargetFramework} + + win-x64;win-arm64;osx-arm64;linux-x64;linux-arm64;linux-musl-x64 + + Major + + enable + enable + aaaaaaaa-bbbb-cccc-dddd-eeeeeeffffff + + + + + true + true + + + true + + + + + true + true + + + + + + + + diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/Microsoft.McpServer.ProjectTemplates.csproj b/src/ProjectTemplates/McpServer.ProjectTemplates/Microsoft.McpServer.ProjectTemplates.csproj new file mode 100644 index 000000000000..ed9df779964e --- /dev/null +++ b/src/ProjectTemplates/McpServer.ProjectTemplates/Microsoft.McpServer.ProjectTemplates.csproj @@ -0,0 +1,24 @@ + + + + $(DefaultNetCoreTargetFramework) + Microsoft.McpServer.ProjectTemplates.$(AspNetCoreMajorMinorVersion) + MCP Server Template Pack for Microsoft Template Engine + + + + + + DefaultNetCoreTargetFramework=$(DefaultNetCoreTargetFramework); + MicrosoftExtensionsHostingVersion=$(MicrosoftExtensionsHostingVersion); + ModelContextProtocolVersion=$(ModelContextProtocolVersion); + ModelContextProtocolAspNetCoreVersion=$(ModelContextProtocolAspNetCoreVersion); + + + + + + + + + diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/content/Directory.Build.props b/src/ProjectTemplates/McpServer.ProjectTemplates/content/Directory.Build.props new file mode 100644 index 000000000000..5e2e6944540a --- /dev/null +++ b/src/ProjectTemplates/McpServer.ProjectTemplates/content/Directory.Build.props @@ -0,0 +1,8 @@ + + + + diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/content/Directory.Build.targets b/src/ProjectTemplates/McpServer.ProjectTemplates/content/Directory.Build.targets new file mode 100644 index 000000000000..0f803ab0e03f --- /dev/null +++ b/src/ProjectTemplates/McpServer.ProjectTemplates/content/Directory.Build.targets @@ -0,0 +1,7 @@ + + + + diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/.template.config/dotnetcli.host.json new file mode 100644 index 000000000000..c371379f9042 --- /dev/null +++ b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/.template.config/dotnetcli.host.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://json.schemastore.org/dotnetcli.host", + "symbolInfo": { + "Transport": { + "longName": "transport", + "shortName": "t" + }, + "NativeAot": { + "longName": "aot", + "shortName": "" + }, + "SelfContained": { + "longName": "self-contained", + "shortName": "" + }, + "Framework": { + "longName": "framework" + }, + "skipRestore": { + "longName": "no-restore", + "shortName": "" + }, + "httpPort": { + "isHidden": true + }, + "httpsPort": { + "isHidden": true + } + }, + "usageExamples": [ + "" + ] +} diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/.template.config/ide.host.json new file mode 100644 index 000000000000..a4971fdf7fc4 --- /dev/null +++ b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/.template.config/ide.host.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json.schemastore.org/ide.host", + "order": 0, + "icon": "ide/icon.ico", + "symbolInfo": [ + { + "id": "Transport", + "isVisible": true + }, + { + "id": "NativeAot", + "isVisible": true + }, + { + "id": "SelfContained", + "isVisible": true + } + ] +} diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/.template.config/ide/icon.ico b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/.template.config/ide/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..954709ffd6b9d360fbc0b121a63a29657e0fe768 GIT binary patch literal 38045 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 +/// Sample MCP tools for demonstration purposes. +/// These tools can be invoked by MCP clients to perform various operations. +/// +internal class RandomNumberTools +{ + [McpServerTool] + [Description("Generates a random number between the specified minimum and maximum values.")] + public int GetRandomNumber( + [Description("Minimum value (inclusive)")] int min = 0, + [Description("Maximum value (exclusive)")] int max = 100) + { + return Random.Shared.Next(min, max); + } +} diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/local/.mcp/server.json b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/local/.mcp/server.json new file mode 100644 index 000000000000..f5b270270d0a --- /dev/null +++ b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/local/.mcp/server.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json", + "description": "", + "name": "io.github./", + "version": "0.1.0-beta", + "packages": [ + { + "registryType": "nuget", + "identifier": "", + "version": "0.1.0-beta", + "transport": { + "type": "stdio" + }, + "packageArguments": [], + "environmentVariables": [] + } + ], + "repository": { + "url": "https://github.com//", + "source": "github" + } +} diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/local/Program.cs b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/local/Program.cs new file mode 100644 index 000000000000..f320c93fd888 --- /dev/null +++ b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/local/Program.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +var builder = Host.CreateApplicationBuilder(args); + +// Configure all logs to go to stderr (stdout is used for the MCP protocol messages). +builder.Logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace); + +// Add the MCP services: the transport to use (stdio) and the tools to register. +builder.Services + .AddMcpServer() + .WithStdioServerTransport() + .WithTools(); + +await builder.Build().RunAsync(); diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/local/README.md b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/local/README.md new file mode 100644 index 000000000000..05c9ac5e26d5 --- /dev/null +++ b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/local/README.md @@ -0,0 +1,104 @@ +# MCP Server + +This README was created using the C# MCP server project template. +It demonstrates how you can easily create an MCP server using C# and publish it as a NuGet package. + +#### ---#if (SelfContained) +The MCP server is built as a self-contained application and does not require the .NET runtime to be installed on the target machine. +However, since it is self-contained, it must be built for each target platform separately. +By default, the template is configured to build for: +* `win-x64` +* `win-arm64` +* `osx-arm64` +* `linux-x64` +* `linux-arm64` +* `linux-musl-x64` + +If your users require more platforms to be supported, update the list of runtime identifiers in the project's `` element. +#### ---#else +The MCP server is built as a framework-dependent application and requires the .NET runtime to be installed on the target machine. +The application is configured to roll-forward to the next highest major version of the runtime if one is available on the target machine. +If an applicable .NET runtime is not available, the MCP server will not start. +Consider building the MCP server as a self-contained application if you want to avoid this dependency. +#### ---#endif + +See [aka.ms/nuget/mcp/guide](https://aka.ms/nuget/mcp/guide) for the full guide. + +## Checklist before publishing to NuGet.org + +- Test the MCP server locally using the steps below. +- Update the package metadata in the .csproj file, in particular the ``. +- Update `.mcp/server.json` to declare your MCP server's inputs. + - See [configuring inputs](https://aka.ms/nuget/mcp/guide/configuring-inputs) for more details. +- Pack the project using `dotnet pack`. + +The `bin/Release` directory will contain the package file (.nupkg), which can be [published to NuGet.org](https://learn.microsoft.com/nuget/nuget-org/publish-a-package). + +## Developing locally + +To test this MCP server from source code (locally) without using a built MCP server package, you can configure your IDE to run the project directly using `dotnet run`. + +```json +{ + "servers": { + "McpServer-CSharp": { + "type": "stdio", + "command": "dotnet", + "args": [ + "run", + "--project", + "" + ] + } + } +} +``` + +Refer to the VS Code or Visual Studio documentation for more information on configuring and using MCP servers: + +- [Use MCP servers in VS Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) +- [Use MCP servers in Visual Studio](https://learn.microsoft.com/visualstudio/ide/mcp-servers) + +## Testing the MCP Server + +Once configured, you can ask Copilot Chat for a random number, for example, `Give me 3 random numbers`. It should prompt you to use the `get_random_number` tool on the `McpServer-CSharp` MCP server and show you the results. + +## Publishing to NuGet.org + +1. Run `dotnet pack -c Release` to create the NuGet package +2. Publish to NuGet.org with `dotnet nuget push bin/Release/*.nupkg --api-key --source https://api.nuget.org/v3/index.json` + +## Using the MCP Server from NuGet.org + +Once the MCP server package is published to NuGet.org, you can configure it in your preferred IDE. Both VS Code and Visual Studio use the `dnx` command to download and install the MCP server package from NuGet.org. + +- **VS Code**: Create a `/.vscode/mcp.json` file +- **Visual Studio**: Create a `\.mcp.json` file + +For both VS Code and Visual Studio, the configuration file uses the following server definition: + +```json +{ + "servers": { + "McpServer-CSharp": { + "type": "stdio", + "command": "dnx", + "args": [ + "", + "--version", + "", + "--yes" + ] + } + } +} +``` + +## More information + +.NET MCP servers use the [ModelContextProtocol](https://www.nuget.org/packages/ModelContextProtocol) C# SDK. For more information about MCP: + +- [Official Documentation](https://modelcontextprotocol.io/) +- [Protocol Specification](https://spec.modelcontextprotocol.io/) +- [GitHub Organization](https://github.com/modelcontextprotocol) +- [MCP C# SDK](https://csharp.sdk.modelcontextprotocol.io/) diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/remote/McpServer-CSharp.http b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/remote/McpServer-CSharp.http new file mode 100644 index 000000000000..f79d8ec14296 --- /dev/null +++ b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/remote/McpServer-CSharp.http @@ -0,0 +1,21 @@ +# For more info on HTTP files go to https://aka.ms/vs/httpfile + +#if (hostIdentifier == "vs") +@HostAddress = https://localhost:9995 +#else +@HostAddress = http://localhost:9996 +#endif + +POST {{HostAddress}}/ +Accept: application/json, text/event-stream +Content-Type: application/json +MCP-Protocol-Version: 2025-11-25 + +{ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "get_random_number" + } +} diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/remote/Program.cs b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/remote/Program.cs new file mode 100644 index 000000000000..47c980c48ab9 --- /dev/null +++ b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/remote/Program.cs @@ -0,0 +1,21 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add the MCP services: the transport to use (http) and the tools to register. +builder.Services + .AddMcpServer() + .WithHttpTransport(options => + { + // Stateless mode is recommended for servers that don't need + // server-to-client requests like sampling or elicitation. + // See https://csharp.sdk.modelcontextprotocol.io/concepts/transports/transports.html for details. + options.Stateless = true; + }) + .WithTools(); + +var app = builder.Build(); +app.MapMcp(); +#if (hostIdentifier == "vs") +app.UseHttpsRedirection(); +#endif + +app.Run(); diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/remote/Properties/launchSettings.json b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/remote/Properties/launchSettings.json new file mode 100644 index 000000000000..37d5661e4deb --- /dev/null +++ b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/remote/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:9996", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:9995;http://localhost:9996", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/remote/README.md b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/remote/README.md new file mode 100644 index 000000000000..dfca0cf7c942 --- /dev/null +++ b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/remote/README.md @@ -0,0 +1,76 @@ +# MCP Server + +This README was created using the C# MCP server project template. +It demonstrates how you can easily create an MCP server using C# and run it as an ASP.NET Core web application. + +#### ---#if (SelfContained) +The MCP server is built as a self-contained application and does not require the .NET runtime to be installed on the target machine. +However, since it is self-contained, it must be built for each target platform separately. +By default, the template is configured to build for: +* `win-x64` +* `win-arm64` +* `osx-arm64` +* `linux-x64` +* `linux-arm64` +* `linux-musl-x64` + +If you require more platforms to be supported, update the list of runtime identifiers in the project's `` element. +#### ---#else +The MCP server is built as a framework-dependent application and requires the ASP.NET Core runtime to be installed on the target machine. +The application is configured to roll-forward to the next highest major version of the runtime if one is available on the target machine. +If an applicable .NET runtime is not available, the MCP server will not start. +Consider building the MCP server as a self-contained application if you want to avoid this dependency. +#### ---#endif + +## Developing locally + +To test this MCP server from source code (locally), you can configure your IDE to connect to the server using localhost. + +#### ---#if (hostIdentifier == "vs") +```json +{ + "servers": { + "McpServer-CSharp": { + "type": "http", + "url": "https://localhost:9995" + } + } +} +``` +#### ---#else +```json +{ + "servers": { + "McpServer-CSharp": { + "type": "http", + "url": "http://localhost:9996" + } + } +} +``` +#### ---#endif + +Refer to the VS Code or Visual Studio documentation for more information on configuring and using MCP servers: + +- [Use MCP servers in VS Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) +- [Use MCP servers in Visual Studio](https://learn.microsoft.com/visualstudio/ide/mcp-servers) + +## Testing the MCP Server + +Once configured, you can ask Copilot Chat for a random number, for example, `Give me 3 random numbers`. It should prompt you to use the `get_random_number` tool on the `McpServer-CSharp` MCP server and show you the results. + +## Known issues + +1. When using VS Code, connecting to `https://localhost:9995` fails. + * This is related to using a self-signed developer certificate, even when the certificate is trusted by the system. + * Connecting with `http://localhost:9996` succeeds. + * See [Cannot connect to MCP server via SSE using trusted developer certificate (microsoft/vscode#248170)](https://github.com/microsoft/vscode/issues/248170) for more information. + +## More information + +ASP.NET Core MCP servers use the [ModelContextProtocol.AspNetCore](https://www.nuget.org/packages/ModelContextProtocol.AspNetCore) package from the MCP C# SDK. For more information about MCP: + +- [Official Documentation](https://modelcontextprotocol.io/) +- [Protocol Specification](https://spec.modelcontextprotocol.io/) +- [GitHub Organization](https://github.com/modelcontextprotocol) +- [MCP C# SDK](https://csharp.sdk.modelcontextprotocol.io/) diff --git a/src/ProjectTemplates/ProjectTemplates.slnf b/src/ProjectTemplates/ProjectTemplates.slnf index acddb24d68b1..d49256df3ee5 100644 --- a/src/ProjectTemplates/ProjectTemplates.slnf +++ b/src/ProjectTemplates/ProjectTemplates.slnf @@ -64,6 +64,7 @@ "src\\ProjectTemplates\\Web.Client.ItemTemplates\\Microsoft.DotNet.Web.Client.ItemTemplates.csproj", "src\\ProjectTemplates\\Web.ItemTemplates\\Microsoft.DotNet.Web.ItemTemplates.csproj", "src\\ProjectTemplates\\Web.ProjectTemplates\\Microsoft.DotNet.Web.ProjectTemplates.csproj", + "src\\ProjectTemplates\\McpServer.ProjectTemplates\\Microsoft.McpServer.ProjectTemplates.csproj", "src\\ProjectTemplates\\test\\Templates.Blazor.Tests\\Templates.Blazor.Tests.csproj", "src\\ProjectTemplates\\test\\Templates.Blazor.WebAssembly.Auth.Tests\\Templates.Blazor.WebAssembly.Auth.Tests.csproj", "src\\ProjectTemplates\\test\\Templates.Blazor.WebAssembly.Tests\\Templates.Blazor.WebAssembly.Tests.csproj", diff --git a/src/ProjectTemplates/README.md b/src/ProjectTemplates/README.md index 7d34c1a26a24..e661125072de 100644 --- a/src/ProjectTemplates/README.md +++ b/src/ProjectTemplates/README.md @@ -10,6 +10,7 @@ The following contains a description of each sub-directory in the `ProjectTempla - `Web.Client.ItemTemplates`: Contains the Web Client-Side File templates, includes things like less, scss, and typescript - `Web.ItemTemplates`: Contains the Web File templates, includes things like: protobuf, razor component, razor page, view import and start pages - `Web.ProjectTemplates`: Contains the ASP.NET Core Web Template pack, including Blazor Server, WASM, Empty, Grpc, Razor Class Library, RazorPages, MVC, WebApi. +- `McpServer.ProjectTemplates`: Contains the standalone MCP Server Template pack. - `migrations`: Contains migration related scripts. - `scripts`: Contains a collection of scripts that help running tests locally that avoid having to install the templates to the machine. - `test`: Contains the template tests. diff --git a/src/ProjectTemplates/Shared/TemplatePackageInstaller.cs b/src/ProjectTemplates/Shared/TemplatePackageInstaller.cs index 08555fb5e696..850c02406c9f 100644 --- a/src/ProjectTemplates/Shared/TemplatePackageInstaller.cs +++ b/src/ProjectTemplates/Shared/TemplatePackageInstaller.cs @@ -38,6 +38,7 @@ internal static class TemplatePackageInstaller "Microsoft.DotNet.Web.ProjectTemplates.9.0", "Microsoft.DotNet.Web.ProjectTemplates.10.0", "Microsoft.AspNetCore.Blazor.Templates", + "Microsoft.McpServer.ProjectTemplates", }; public static string CustomHivePath { get; } = Path.GetFullPath((string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix"))) @@ -97,7 +98,7 @@ private static async Task InstallTemplatePackages(ITestOutputHelper output) throw new InvalidOperationException($"Failed to find required templates in {packagesDir}. Please ensure the *Templates*.nupkg have been built."); } - Assert.Equal(3, builtPackages.Length); + Assert.Equal(4, builtPackages.Length); await VerifyCannotFindTemplateAsync(output, "web"); await VerifyCannotFindTemplateAsync(output, "webapp"); diff --git a/src/ProjectTemplates/scripts/Run-McpServer-Locally.ps1 b/src/ProjectTemplates/scripts/Run-McpServer-Locally.ps1 new file mode 100644 index 000000000000..af7208f9e6e1 --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-McpServer-Locally.ps1 @@ -0,0 +1,12 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "mcpserver" "mcpserver" "Microsoft.McpServer.ProjectTemplates.10.0.10.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/test/Templates.Tests/McpServerTemplateTest.cs b/src/ProjectTemplates/test/Templates.Tests/McpServerTemplateTest.cs new file mode 100644 index 000000000000..03e8ce0b7dd0 --- /dev/null +++ b/src/ProjectTemplates/test/Templates.Tests/McpServerTemplateTest.cs @@ -0,0 +1,143 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.InternalTesting; +using Templates.Test.Helpers; +using Xunit.Abstractions; + +namespace Templates.Test; + +#pragma warning disable xUnit1041 // Fixture arguments to test classes must have fixture sources + +public class McpServerTemplateTest : LoggedTest +{ + public McpServerTemplateTest(ProjectFactoryFixture projectFactory) + { + ProjectFactory = projectFactory; + } + + public ProjectFactoryFixture ProjectFactory { get; } + private ITestOutputHelper _output; + public ITestOutputHelper Output + { + get + { + if (_output == null) + { + _output = new TestOutputLogger(Logger); + } + return _output; + } + } + + [ConditionalFact] + [SkipOnHelix("Self-contained template requires runtime packages unavailable in CI")] + public async Task McpServerTemplate_Local() + { + await McpServerTemplateCoreAsync("local"); + } + + [ConditionalFact] + [SkipOnHelix("Self-contained template requires runtime packages unavailable in CI")] + public async Task McpServerTemplate_Remote() + { + await McpServerTemplateCoreAsync("remote"); + } + + [ConditionalFact] + public async Task McpServerTemplate_Local_SelfContainedFalse() + { + await McpServerTemplateCoreAsync("local", args: ["--self-contained", "false"]); + } + + [ConditionalFact] + public async Task McpServerTemplate_Remote_SelfContainedFalse() + { + await McpServerTemplateCoreAsync("remote", args: ["--self-contained", "false"]); + } + + [ConditionalFact] + [SkipOnHelix("NativeAOT template requires runtime packages unavailable in CI")] + public async Task McpServerTemplate_Local_NativeAot() + { + await McpServerTemplateCoreAsync("local", args: [ArgConstants.PublishNativeAot]); + } + + [ConditionalFact] + [SkipOnHelix("NativeAOT template requires runtime packages unavailable in CI")] + public async Task McpServerTemplate_Remote_NativeAot() + { + await McpServerTemplateCoreAsync("remote", args: [ArgConstants.PublishNativeAot]); + } + + private async Task McpServerTemplateCoreAsync(string transport, string[] args = null) + { + var nativeAot = args?.Contains(ArgConstants.PublishNativeAot) ?? false; + + var project = await ProjectFactory.CreateProject(Output); + if (nativeAot) + { + project.SetCurrentRuntimeIdentifier(); + } + + var allArgs = new List { "--transport", transport }; + if (args is not null) + { + allArgs.AddRange(args); + } + + await project.RunDotNetNewAsync("mcpserver", args: allArgs.ToArray()); + + if (transport == "remote") + { + var expectedLaunchProfileNames = new[] { "http", "https" }; + await project.VerifyLaunchSettings(expectedLaunchProfileNames); + } + + if (nativeAot) + { + await project.VerifyHasProperty("InvariantGlobalization", "true"); + } + + // Force a restore if native AOT so that RID-specific assets are restored + await project.RunDotNetPublishAsync(noRestore: !nativeAot); + + // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release + // The output from publish will go into bin/Release/netcoreappX.Y/publish and won't be affected by calling build + // later, while the opposite is not true. + await project.RunDotNetBuildAsync(); + + if (transport == "local") + { + using (var aspNetProcess = project.StartBuiltProjectAsync(hasListeningUri: false)) + { + Assert.False( + aspNetProcess.Process.HasExited, + ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", project, aspNetProcess.Process)); + } + + using (var aspNetProcess = project.StartPublishedProjectAsync(hasListeningUri: false, usePublishedAppHost: nativeAot)) + { + Assert.False( + aspNetProcess.Process.HasExited, + ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", project, aspNetProcess.Process)); + } + } + else + { + using (var aspNetProcess = project.StartBuiltProjectAsync(hasListeningUri: true)) + { + Assert.False( + aspNetProcess.Process.HasExited, + ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", project, aspNetProcess.Process)); + } + + using (var aspNetProcess = project.StartPublishedProjectAsync(hasListeningUri: true, usePublishedAppHost: nativeAot)) + { + Assert.False( + aspNetProcess.Process.HasExited, + ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", project, aspNetProcess.Process)); + } + } + } +} diff --git a/src/ProjectTemplates/test/Templates.Tests/Templates.Tests.csproj b/src/ProjectTemplates/test/Templates.Tests/Templates.Tests.csproj index df91970f0576..4cb2f6abfe98 100644 --- a/src/ProjectTemplates/test/Templates.Tests/Templates.Tests.csproj +++ b/src/ProjectTemplates/test/Templates.Tests/Templates.Tests.csproj @@ -39,6 +39,7 @@ + @@ -68,6 +69,10 @@ Private="false" ReferenceOutputAssembly="false" SkipGetTargetFrameworkProperties="true" /> + diff --git a/src/ProjectTemplates/test/Templates.Tests/template-baselines.json b/src/ProjectTemplates/test/Templates.Tests/template-baselines.json index 61b2ffddf049..c9aa85dcae5d 100644 --- a/src/ProjectTemplates/test/Templates.Tests/template-baselines.json +++ b/src/ProjectTemplates/test/Templates.Tests/template-baselines.json @@ -3719,6 +3719,28 @@ "wwwroot/index.html", "wwwroot/css/app.css" ] + }, + "mcpserver": { + "Local": { + "Template": "mcpserver", + "Arguments": "new mcpserver --transport local", + "Files": [ + "Program.cs", + "README.md", + ".mcp/server.json", + "Tools/RandomNumberTools.cs" + ] + }, + "Remote": { + "Template": "mcpserver", + "Arguments": "new mcpserver --transport remote", + "Files": [ + "Program.cs", + "README.md", + "{ProjectName}.http", + "Properties/launchSettings.json", + "Tools/RandomNumberTools.cs" + ] } } } From 126841fab8798341dd5ce12c75d856e1c68cebd2 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Sat, 9 May 2026 01:17:15 -0700 Subject: [PATCH 2/2] Fix merge error - missing brace in baselines json --- .../test/Templates.Tests/template-baselines.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ProjectTemplates/test/Templates.Tests/template-baselines.json b/src/ProjectTemplates/test/Templates.Tests/template-baselines.json index c9aa85dcae5d..da227105502b 100644 --- a/src/ProjectTemplates/test/Templates.Tests/template-baselines.json +++ b/src/ProjectTemplates/test/Templates.Tests/template-baselines.json @@ -3719,6 +3719,7 @@ "wwwroot/index.html", "wwwroot/css/app.css" ] + } }, "mcpserver": { "Local": {