diff --git a/AspNetCore.slnx b/AspNetCore.slnx
index dac5f03bb6cb..917cdafc5f67 100644
--- a/AspNetCore.slnx
+++ b/AspNetCore.slnx
@@ -775,6 +775,7 @@
+
diff --git a/eng/Build.props b/eng/Build.props
index 2d0cf067a556..da1a3f26306f 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 ee7006b07210..39b1cef01649 100644
--- a/eng/Dependencies.props
+++ b/eng/Dependencies.props
@@ -215,6 +215,8 @@ may be turned into `` items in projects.
+
+
diff --git a/eng/Versions.props b/eng/Versions.props
index 0aac744e93b8..aeea0ec00b19 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -157,6 +157,8 @@
3.14.1
0.3.269
$(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 000000000000..954709ffd6b9
Binary files /dev/null and b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/.template.config/ide/icon.ico differ
diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/.template.config/template.json b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/.template.config/template.json
new file mode 100644
index 000000000000..a84a21c30518
--- /dev/null
+++ b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/.template.config/template.json
@@ -0,0 +1,223 @@
+{
+ "$schema": "http://json.schemastore.org/template",
+ "author": "Microsoft",
+ "classifications": [
+ "Common",
+ "AI",
+ "MCP"
+ ],
+ "name": "MCP Server App",
+ "description": "A project template for creating a Model Context Protocol (MCP) server using C# and the ModelContextProtocol package.",
+ "groupIdentity": "Microsoft.McpServer",
+ "precedence": "11000",
+ "identity": "Microsoft.McpServer.CSharp.11.0",
+ "shortName": "mcpserver",
+ "defaultName": "McpServer",
+ "sourceName": "McpServer-CSharp",
+ "preferNameDirectory": true,
+ "tags": {
+ "language": "C#",
+ "type": "project"
+ },
+ "guids": [
+ "aaaaaaaa-bbbb-cccc-dddd-eeeeeeffffff"
+ ],
+ "symbols": {
+ "hostIdentifier": {
+ "type": "bind",
+ "binding": "HostIdentifier"
+ },
+ "Transport": {
+ "type": "parameter",
+ "displayName": "The MCP server _transport type to use",
+ "description": "Whether to create a 'local' (stdio transport) or 'remote' (http transport) MCP server",
+ "datatype": "choice",
+ "choices": [
+ {
+ "choice": "local",
+ "description": "A console application will be created to use the stdio transport as a local MCP server"
+ },
+ {
+ "choice": "remote",
+ "description": "An ASP.NET Core web application will be created to use the http transport as a remote MCP server"
+ }
+ ],
+ "defaultValue": "local"
+ },
+ "NativeAot": {
+ "type": "parameter",
+ "datatype": "bool",
+ "defaultValue": "false",
+ "displayName": "Enable _native AOT publish",
+ "description": "Whether to enable the MCP server for publishing as a native AOT application."
+ },
+ "SelfContained": {
+ "type": "parameter",
+ "datatype": "bool",
+ "defaultValue": "true",
+ "displayName": "Enable _self-contained publish",
+ "description": "Whether to enable the MCP server for publishing as a self-contained application."
+ },
+ "Framework": {
+ "type": "parameter",
+ "description": "The target framework for the project.",
+ "datatype": "choice",
+ "choices": [
+ {
+ "choice": "net11.0"
+ }
+ ],
+ "replaces": "net11.0",
+ "defaultValue": "net11.0"
+ },
+ "skipRestore": {
+ "type": "parameter",
+ "datatype": "bool",
+ "description": "If specified, skips the automatic restore of the project on create.",
+ "defaultValue": "false"
+ },
+ "httpsPort": {
+ "type": "parameter",
+ "datatype": "integer",
+ "description": "Port number to use for the HTTPS endpoint in launchSettings.json."
+ },
+ "httpsPortGenerated": {
+ "type": "generated",
+ "generator": "port",
+ "parameters": {
+ "low": 5000,
+ "high": 5300
+ }
+ },
+ "httpsPortReplacer": {
+ "type": "generated",
+ "generator": "coalesce",
+ "parameters": {
+ "sourceVariableName": "httpsPort",
+ "fallbackVariableName": "httpsPortGenerated"
+ },
+ "replaces": "9995",
+ "onlyIf": [{
+ "after": "localhost:"
+ }]
+ },
+ "httpPort": {
+ "type": "parameter",
+ "datatype": "integer",
+ "description": "Port number to use for the HTTP endpoint in launchSettings.json."
+ },
+ "httpPortGenerated": {
+ "type": "generated",
+ "generator": "port",
+ "parameters": {
+ "low": 6000,
+ "high": 6300
+ }
+ },
+ "httpPortReplacer": {
+ "type": "generated",
+ "generator": "coalesce",
+ "parameters": {
+ "sourceVariableName": "httpPort",
+ "fallbackVariableName": "httpPortGenerated"
+ },
+ "replaces": "9996",
+ "onlyIf": [{
+ "after": "localhost:"
+ }]
+ },
+ "IsTransportRemote": {
+ "type": "computed",
+ "value": "(Transport == \"remote\")"
+ },
+ "IsTransportLocal": {
+ "type": "computed",
+ "value": "(!IsTransportRemote)"
+ }
+ },
+ "primaryOutputs": [
+ {
+ "path": "./README.md"
+ },
+ {
+ "path": "./McpServer-CSharp.csproj"
+ }
+ ],
+ "sources": [
+ {
+ "source": "./common",
+ "target": "./"
+ },
+ {
+ "condition": "(IsTransportLocal)",
+ "source": "./local",
+ "target": "./"
+ },
+ {
+ "condition": "(IsTransportRemote)",
+ "source": "./remote",
+ "target": "./"
+ }
+ ],
+ "postActions": [
+ {
+ "id": "restore",
+ "condition": "(!skipRestore)",
+ "description": "Restore NuGet packages required by this project.",
+ "manualInstructions": [
+ {
+ "text": "Run 'dotnet restore'"
+ }
+ ],
+ "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
+ "args": {
+ "files": ["**/*.csproj"]
+ },
+ "continueOnError": true
+ },
+ {
+ "condition": "(hostIdentifier != \"dotnetcli\" && hostIdentifier != \"dotnetcli-preview\")",
+ "description": "Opens README file in the editor",
+ "manualInstructions": [],
+ "actionId": "84C0DA21-51C8-4541-9940-6CA19AF04EE6",
+ "args": {
+ "files": "0"
+ },
+ "continueOnError": true
+ }
+ ],
+ "SpecialCustomOperations": {
+ "**/*.md": {
+ "operations": [
+ {
+ "type": "conditional",
+ "configuration": {
+ "if": [ "#### ---#if" ],
+ "else": [ "#### ---#else" ],
+ "elseif": [ "#### ---#elseif", "#### ---#elif" ],
+ "endif": [ "#### ---#endif" ],
+ "trim": "true",
+ "wholeLine": "true",
+ "evaluator": "C++"
+ }
+ }
+ ]
+ },
+ "**/*.http": {
+ "operations": [
+ {
+ "type": "conditional",
+ "configuration": {
+ "if": [ "#if" ],
+ "else": [ "#else" ],
+ "elseif": [ "#elseif", "#elif" ],
+ "endif": [ "#endif" ],
+ "trim": "true",
+ "wholeLine": "true",
+ "evaluator": "C++"
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/common/Tools/RandomNumberTools.cs b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/common/Tools/RandomNumberTools.cs
new file mode 100644
index 000000000000..568574f47d96
--- /dev/null
+++ b/src/ProjectTemplates/McpServer.ProjectTemplates/content/McpServer-CSharp/common/Tools/RandomNumberTools.cs
@@ -0,0 +1,18 @@
+using System.ComponentModel;
+using ModelContextProtocol.Server;
+
+///
+/// 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 def41d4a37aa..480d0aa56336 100644
--- a/src/ProjectTemplates/Shared/TemplatePackageInstaller.cs
+++ b/src/ProjectTemplates/Shared/TemplatePackageInstaller.cs
@@ -39,6 +39,7 @@ internal static class TemplatePackageInstaller
"Microsoft.DotNet.Web.ProjectTemplates.10.0",
"Microsoft.DotNet.Web.ProjectTemplates.11.0",
"Microsoft.AspNetCore.Blazor.Templates",
+ "Microsoft.McpServer.ProjectTemplates",
};
public static string CustomHivePath { get; } = Path.GetFullPath((string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix")))
@@ -98,7 +99,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..7b8730623b1a
--- /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.11.0.11.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 28f6c92fe185..fc55178f5d62 100644
--- a/src/ProjectTemplates/test/Templates.Tests/Templates.Tests.csproj
+++ b/src/ProjectTemplates/test/Templates.Tests/Templates.Tests.csproj
@@ -39,6 +39,7 @@
+
@@ -70,6 +71,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 3e8d66e5af62..154c905637f3 100644
--- a/src/ProjectTemplates/test/Templates.Tests/template-baselines.json
+++ b/src/ProjectTemplates/test/Templates.Tests/template-baselines.json
@@ -3767,5 +3767,28 @@
"wwwroot/{ProjectName}.lib.module.js"
]
}
+ },
+ "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"
+ ]
+ }
}
}