diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1348358b06a0d..d036e252a9596 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -933,7 +933,7 @@ updates: patterns: - "*" # Prefer a single PR per project update. - package-ecosystem: "nuget" - directory: "/docs/ai/quickstarts/snippets/mcp-server" #MinimalMcpServer.csproj + directory: "/docs/ai/quickstarts/snippets/mcp-server" #SampleMcpServer.csproj schedule: interval: "weekly" day: "wednesday" diff --git a/docs/ai/media/mcp/available-tools-nuget.png b/docs/ai/media/mcp/available-tools-nuget.png new file mode 100644 index 0000000000000..470a9f1886652 Binary files /dev/null and b/docs/ai/media/mcp/available-tools-nuget.png differ diff --git a/docs/ai/media/mcp/missing-dnx.png b/docs/ai/media/mcp/missing-dnx.png new file mode 100644 index 0000000000000..fdf8e0690bd38 Binary files /dev/null and b/docs/ai/media/mcp/missing-dnx.png differ diff --git a/docs/ai/media/mcp/nuget-mcp-display.png b/docs/ai/media/mcp/nuget-mcp-display.png new file mode 100644 index 0000000000000..c44b02c665db0 Binary files /dev/null and b/docs/ai/media/mcp/nuget-mcp-display.png differ diff --git a/docs/ai/media/mcp/nuget-mcp-search.png b/docs/ai/media/mcp/nuget-mcp-search.png new file mode 100644 index 0000000000000..068d631424dea Binary files /dev/null and b/docs/ai/media/mcp/nuget-mcp-search.png differ diff --git a/docs/ai/quickstarts/build-mcp-server.md b/docs/ai/quickstarts/build-mcp-server.md index ef814e68c645d..4868d06f36d9c 100644 --- a/docs/ai/quickstarts/build-mcp-server.md +++ b/docs/ai/quickstarts/build-mcp-server.md @@ -1,60 +1,54 @@ --- -title: Quickstart - Create a minimal MCP Server using .NET -description: Learn to create and connect to a minimal MCP server using .NET -ms.date: 05/21/2025 +title: Quickstart - Create a minimal MCP server and publish to NuGet +description: Learn to create and connect to a minimal MCP server using C# and publish it to NuGet. +ms.date: 07/02/2025 ms.topic: quickstart ms.custom: devx-track-dotnet, devx-track-dotnet-ai author: alexwolfmsft ms.author: alexwolf --- -# Create and connect to a minimal MCP server using .NET +# Create a minimal MCP server using C# and publish to NuGet -In this quickstart, you create a minimal Model Context Protocol (MCP) server using the [C# SDK for MCP](https://github.com/modelcontextprotocol/csharp-sdk) and connect to it using GitHub Copilot. MCP servers are services that expose capabilities to clients through the Model Context Protocol (MCP). +In this quickstart, you create a minimal Model Context Protocol (MCP) server using the [C# SDK for MCP](https://github.com/modelcontextprotocol/csharp-sdk), connect to it using GitHub Copilot, and publish it to NuGet. MCP servers are services that expose capabilities to clients through the Model Context Protocol (MCP). + +> [!NOTE] +> The `Microsoft.Extensions.AI.Templates` experience is currently in preview. The template uses the [ModelContextProtocol](https://www.nuget.org/packages/ModelContextProtocol/) library and the [MCP registry `server.json` schema](https://github.com/modelcontextprotocol/registry/blob/main/docs/server-json/README.md), which are both in preview. ## Prerequisites -- [.NET 8.0 SDK or higher](https://dotnet.microsoft.com/download) +- [.NET 10.0 SDK](https://dotnet.microsoft.com/download/dotnet) (preview 6 or higher) - [Visual Studio Code](https://code.visualstudio.com/) - [GitHub Copilot extension](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) for Visual Studio Code +- [NuGet.org account](https://www.nuget.org/users/account/LogOn) ## Create the project -1. In a terminal window, navigate to the directory where you want to create your app, and create a new console app with the `dotnet new` command: +1. In a terminal window, install the MCP Server template: ```bash - dotnet new console -n MinimalMcpServer + dotnet new install Microsoft.Extensions.AI.Templates::9.6.0-TBD ``` -1. Navigate to the `MinimalMcpServer` directory: +1. Create a new MCP server app with the `dotnet new mcpserver` command: ```bash - cd MinimalMcpServer + dotnet new mcpserver -n SampleMcpServer ``` -1. Add the following NuGet packages to your app: +1. Navigate to the `SampleMcpServer` directory: ```bash - dotnet add package ModelContextProtocol --prerelease - dotnet add package Microsoft.Extensions.Hosting + cd SampleMcpServer ``` - - The [ModelContextProtocol](https://www.nuget.org/packages/ModelContextProtocol) package is the official C# SDK for working with the Model Context Protocol. - - The [Microsoft.Extensions.Hosting](https://www.nuget.org/packages/Microsoft.Extensions.Hosting) package provides the generic .NET `HostBuilder` and services for logging and dependency injection. - -## Add the app code - -Replace the contents of `Program.cs` with the following code to implement a minimal MCP server that exposes simple echo tools. The AI model invokes these tools as necessary to generate responses to user prompts. +1. Build the project: -:::code language="csharp" source="snippets/mcp-server/program.cs" ::: - -The preceding code: + ```bash + dotnet build + ``` -- Creates a generic host builder for dependency injection, logging, and configuration. -- Configures logging for better integration with MCP clients. -- Registers the MCP server, configures it to use stdio transport, and scans the assembly for tool definitions. -- Builds and runs the host, which starts the MCP server. -- Defines a static class to hold two MCP tools that echo values back to the client. +1. Update the `` in the `.csproj` file to be unique on NuGet.org, for example `.SampleMcpServer`. ## Configure the MCP server in Visual Studio Code @@ -66,15 +60,14 @@ Configure GitHub Copilot for Visual Studio Code to use your custom MCP server: ```json { - "inputs": [], "servers": { - "MinimalMcpServer": { + "SampleMcpServer": { "type": "stdio", "command": "dotnet", "args": [ "run", "--project", - "${workspaceFolder}/MinimalMcpServer.csproj" + "" ] } } @@ -85,30 +78,206 @@ Configure GitHub Copilot for Visual Studio Code to use your custom MCP server: ## Test the MCP server -1. Open GitHub Copilot in Visual Studio Code and switch to agent mode. -1. Select the **Select tools** icon to verify your **MinimalMcpServer** is available with both tools listed. +The MCP server template includes a tool called `get_random_number` you can use for testing and as a starting point for development. - :::image type="content" source="../media/mcp/available-tools.png" alt-text="A screenshot showing the available MCP tools."::: +1. Open GitHub Copilot in Visual Studio Code and switch to chat mode. -1. Enter a prompt to run the **ReverseEcho** tool: +1. Select the **Select tools** icon to verify your **SampleMcpServer** is available with the sample tool listed. + + :::image type="content" source="../media/mcp/available-tools-nuget.png" alt-text="A screenshot showing the available MCP tools."::: + +1. Enter a prompt to run the **get_random_number** tool: ```console - Reverse the following: "Hello, minimal MCP server!" + Give me a random number between 1 and 100. ``` -1. GitHub Copilot requests permission to run the **ReverseEcho** tool for your prompt. Select **Continue** or use the arrow to select a more specific behavior: +1. GitHub Copilot requests permission to run the **get_random_number** tool for your prompt. Select **Continue** or use the arrow to select a more specific behavior: - **Current session** always runs the operation in the current GitHub Copilot Agent Mode session. - **Current workspace** always runs the command for the current Visual Studio Code workspace. - **Always allow** sets the operation to always run for any GitHub Copilot Agent Mode session or any Visual Studio Code workspace. -1. Verify that the server responds with the echoed message: +1. Verify that the server responds with a random number: ```output - !revres PCM laminim ,olleH + Your random number is 42. + ``` + +## Add inputs and configuration options + +In this example, you enhance the MCP server to use a configuration value set in an environment variable. This could be configuration needed for the functioning of your MCP server, such as an API key, an endpoint to connect to, or a local directory path. + +1. Add another tool method after the `GetRandomNumber` method in `Tools/RandomNumberTools.cs`. Update the tool code to use an environment variable. + + :::code language="csharp" source="snippets/mcp-server/Tools/RandomNumberTools.cs" range="19-36"::: + +1. Update the `.vscode/mcp.json` to set the `WEATHER_CHOICES` environment variable for testing. + + ```json + { + "servers": { + "SampleMcpServer": { + "type": "stdio", + "command": "dotnet", + "args": [ + "run", + "--project", + "" + ], + "env": { + "WEATHER_CHOICES": "sunny,humid,freezing" + } + } + } + } + ``` + +1. Try another prompt with Copilot in VS Code, such as: + + ```console + What is the weather in Redmond, Washington? ``` + VS Code should return a random weather description. + +1. Update the `.mcp/server.json` to declare your environment variable input. The `server.json` file schema is defined by the [MCP Registry project](https://github.com/modelcontextprotocol/registry/blob/main/docs/server-json/README.md) and is used by NuGet.org to generate VS Code MCP configuration. + + * Use the `environment_variables` property to declare environment variables used by your app that will be set by the client using the MCP server (for example, VS Code). + + * Use the `package_arguments` property to define CLI arguments that will be passed to your app. For more examples, see the [MCP Registry project](https://github.com/modelcontextprotocol/registry/blob/main/docs/server-json/examples.md). + + ```json + { + "description": "", + "name": "io.github./", + "packages": [ + { + "registry_name": "nuget", + "name": "", + "version": "", + "package_arguments": [], + "environment_variables": [ + { + "name": "WEATHER_CHOICES", + "description": "Comma separated list of weather descriptions to randomly select.", + "is_required": true, + "is_secret": false + } + ] + } + ], + "repository": { + "url": "https://github.com//", + "source": "github" + }, + "version_detail": { + "version": "0.1.0-beta" + } + } + ``` + + The only information used by NuGet.org in the `server.json` is the first `packages` array item with the `registry_name` value matching `nuget`. The other top-level properties aside from the `packages` property are currently unused and are intended for the upcoming central MCP Registry. You can leave the placeholder values until the MCP Registry is live and ready to accept MCP server entries. + +You can [test your MCP server again](#test-the-mcp-server) before moving forward. + +## Pack and publish to NuGet + +1. Pack the project: + + ```bash + dotnet pack -c Release + ``` + +1. Publish the package to NuGet: + + ```bash + dotnet nuget push bin/Release/*.nupkg --api-key --source https://api.nuget.org/v3/index.json + ``` + + If you want to test the publishing flow before publishing to NuGet.org, you can register an account on the NuGet Gallery integration environment: [https://int.nugettest.org](https://int.nugettest.org). The `push` command would be modified to: + + ```bash + dotnet nuget push bin/Release/*.nupkg --api-key --source https://apiint.nugettest.org/v3/index.json + ``` + +For more information, see [Publish a package](/nuget/nuget-org/publish-a-package). + +## Discover MCP servers on NuGet.org + +1. Search for your MCP server package on [NuGet.org](https://www.nuget.org/packages?packagetype=mcpserver) (or [int.nugettest.org](https://int.nugettest.org/packages?packagetype=mcpserver) if you published to the integration environment) and select it from the list. + + :::image type="content" source="../media/mcp/nuget-mcp-search.png" alt-text="A screenshot showing a search for MCP servers on NuGet.org."::: + +1. View the package details and copy the JSON from the "MCP Server" tab. + + :::image type="content" source="../media/mcp/nuget-mcp-display.png" alt-text="A screenshot showing a specific MCP server displayed on NuGet.org."::: + +1. In your `mcp.json` file in the `.vscode` folder, add the copied JSON, which looks like this: + + ```json + { + "inputs": [ + { + "type": "promptString", + "id": "weather-choices", + "description": "Comma separated list of weather descriptions to randomly select.", + "password": false + } + ], + "servers": { + "Contoso.SampleMcpServer": { + "type": "stdio", + "command": "dnx", + "args": [ + "Contoso.SampleMcpServer", + "--version", + "0.0.1-beta", + "--yes" + ], + "env": { + "WEATHER_CHOICES": "${input:weather-choices}" + } + } + } + } + ``` + + If you published to the NuGet Gallery integration environment, you need to add `"--add-source", "https://apiint.nugettest.org/v3/index.json"` at the end of the `"args"` array. + +1. Save the file. + +1. In GitHub Copilot, select the **Select tools** icon to verify your **SampleMcpServer** is available with the tools listed. + +1. Enter a prompt to run the new **get_city_weather** tool: + + ```console + What is the weather in Redmond? + ``` + +1. If you added inputs to your MCP server (for example, `WEATHER_CHOICES`), you will be prompted to provide values. + +1. Verify that the server responds with the random weather: + + ```output + The weather in Redmond is balmy. + ``` + +## Common issues + +### The command "dnx" needed to run SampleMcpServer was not found. + +If VS Code shows this error when starting the MCP server, you need to install a compatible version of the .NET SDK. + +:::image type="content" source="../media/mcp/missing-dnx.png" alt-text="A screenshot showing the missing dnx command in VS Code."::: + +The `dnx` command is shipped as part of the .NET SDK, starting with version 10 preview 6. [Install the .NET 10 SDK](https://dotnet.microsoft.com/download/dotnet) to resolve this issue. + ## Related content -[Build a minimal MCP client](build-mcp-client.md) -[Get started with .NET AI and the Model Context Protocol](../get-started-mcp.md) +- [Get started with .NET AI and the Model Context Protocol](../get-started-mcp.md) +- [Model Context Protocol .NET samples](https://github.com/microsoft/mcp-dotnet-samples) +- [Build a minimal MCP client](build-mcp-client.md) +- [Publish a package](/nuget/nuget-org/publish-a-package) +- [Find and evaluate NuGet packages for your project](/nuget/consume-packages/finding-and-choosing-packages) +- [What's new in .NET 10](../../core/whats-new/dotnet-10/overview.md) diff --git a/docs/ai/quickstarts/snippets/mcp-server/.mcp/server.json b/docs/ai/quickstarts/snippets/mcp-server/.mcp/server.json new file mode 100644 index 0000000000000..40f5eb490d097 --- /dev/null +++ b/docs/ai/quickstarts/snippets/mcp-server/.mcp/server.json @@ -0,0 +1,27 @@ +{ + "description": "", + "name": "io.github./", + "packages": [ + { + "registry_name": "nuget", + "name": "SampleMcpServer", + "version": "0.1.0-beta", + "package_arguments": [], + "environment_variables": [ + { + "name": "WEATHER_CHOICES", + "description": "Comma separated list of weather descriptions to randomly select.", + "is_required": true, + "is_secret": false + } + ] + } + ], + "repository": { + "url": "https://github.com//", + "source": "github" + }, + "version_detail": { + "version": "0.1.0-beta" + } +} diff --git a/docs/ai/quickstarts/snippets/mcp-server/.vscode/mcp.json b/docs/ai/quickstarts/snippets/mcp-server/.vscode/mcp.json new file mode 100644 index 0000000000000..c44929bfe4921 --- /dev/null +++ b/docs/ai/quickstarts/snippets/mcp-server/.vscode/mcp.json @@ -0,0 +1,16 @@ +{ + "servers": { + "SampleMcpServer": { + "type": "stdio", + "command": "dotnet", + "args": [ + "run", + "--project", + "." + ], + "env": { + "WEATHER_CHOICES": "sunny,humid,freezing" + } + } + } +} diff --git a/docs/ai/quickstarts/snippets/mcp-server/MinimalMcpServer.csproj b/docs/ai/quickstarts/snippets/mcp-server/MinimalMcpServer.csproj deleted file mode 100644 index 14eb339e2ba4f..0000000000000 --- a/docs/ai/quickstarts/snippets/mcp-server/MinimalMcpServer.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - Exe - net9.0 - enable - enable - - - - - - - - diff --git a/docs/ai/quickstarts/snippets/mcp-server/Program.cs b/docs/ai/quickstarts/snippets/mcp-server/Program.cs index dabca53227169..f320c93fd8883 100644 --- a/docs/ai/quickstarts/snippets/mcp-server/Program.cs +++ b/docs/ai/quickstarts/snippets/mcp-server/Program.cs @@ -1,38 +1,16 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using ModelContextProtocol.Server; -using System.ComponentModel; -// Create a generic host builder for -// dependency injection, logging, and configuration. var builder = Host.CreateApplicationBuilder(args); -// Configure logging for better integration with MCP clients. -builder.Logging.AddConsole(consoleLogOptions => -{ -consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace; -}); +// Configure all logs to go to stderr (stdout is used for the MCP protocol messages). +builder.Logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace); -// Register the MCP server and configure it to use stdio transport. -// Scan the assembly for tool definitions. +// Add the MCP services: the transport to use (stdio) and the tools to register. builder.Services .AddMcpServer() .WithStdioServerTransport() - .WithToolsFromAssembly(); + .WithTools(); -// Build and run the host. This starts the MCP server. await builder.Build().RunAsync(); - -// Define a static class to hold MCP tools. -[McpServerToolType] -public static class EchoTool -{ - // Expose a tool that echoes the input message back to the client. - [McpServerTool, Description("Echoes the message back to the client.")] - public static string Echo(string message) => $"Hello from C#: {message}"; - - // Expose a tool that returns the input message in reverse. - [McpServerTool, Description("Echoes in reverse the message sent by the client.")] - public static string ReverseEcho(string message) => new string(message.Reverse().ToArray()); -} \ No newline at end of file diff --git a/docs/ai/quickstarts/snippets/mcp-server/README.md b/docs/ai/quickstarts/snippets/mcp-server/README.md new file mode 100644 index 0000000000000..785237d22791b --- /dev/null +++ b/docs/ai/quickstarts/snippets/mcp-server/README.md @@ -0,0 +1,80 @@ +# MCP Server + +This README was created using the C# MCP server template project. It demonstrates how you can easily create an MCP server using C# and then package it in a NuGet package. + +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). + +## Using the MCP Server in VS Code + +Once the MCP server package is published to NuGet.org, you can use the following VS Code user configuration to download and install the MCP server package. See [Use MCP servers in VS Code (Preview)](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) for more information about using MCP servers in VS Code. + +```json +{ + "mcp": { + "servers": { + "SampleMcpServer": { + "type": "stdio", + "command": "dnx", + "args": [ + "SampleMcpServer", + "--version", + "0.1.0-beta", + "--yes" + ] + } + } + } +} +``` + +Now 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 `SampleMcpServer` MCP server and show you the results. + +## Developing locally in VS Code + +To test this MCP server from source code (locally) without using a built MCP server package, create a `.vscode/mcp.json` file (a VS Code workspace settings file) in your project directory and add the following configuration: + +```json +{ + "servers": { + "SampleMcpServer": { + "type": "stdio", + "command": "dotnet", + "args": [ + "run", + "--project", + "" + ] + } + } +} +``` + +Alternatively, you can configure your VS Code user settings to use your local project: + +```json +{ + "mcp": { + "servers": { + "SampleMcpServer": { + "type": "stdio", + "command": "dotnet", + "args": [ + "run", + "--project", + "" + ] + } + } + } +} +``` diff --git a/docs/ai/quickstarts/snippets/mcp-server/SampleMcpServer.csproj b/docs/ai/quickstarts/snippets/mcp-server/SampleMcpServer.csproj new file mode 100644 index 0000000000000..03593748035e7 --- /dev/null +++ b/docs/ai/quickstarts/snippets/mcp-server/SampleMcpServer.csproj @@ -0,0 +1,32 @@ + + + + net10.0 + Exe + enable + enable + + + true + McpServer + + + README.md + SampleMcpServer + 0.1.0-beta + AI; MCP; server; stdio + An MCP server using the MCP C# SDK. + + + + + + + + + + + + + + diff --git a/docs/ai/quickstarts/snippets/mcp-server/Tools/RandomNumberTools.cs b/docs/ai/quickstarts/snippets/mcp-server/Tools/RandomNumberTools.cs new file mode 100644 index 0000000000000..e355a8608fd22 --- /dev/null +++ b/docs/ai/quickstarts/snippets/mcp-server/Tools/RandomNumberTools.cs @@ -0,0 +1,37 @@ +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(Name = "get_random_number")] + [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); + } + + [McpServerTool(Name = "get_city_weather")] + [Description("Describes random weather in the provided city.")] + public string GetCityWeather( + [Description("Name of the city to return weather for")] string city) + { + // Read the environment variable during tool execution. + // Alternatively, this could be read during startup and passed via IOptions dependency injection + var weather = Environment.GetEnvironmentVariable("WEATHER_CHOICES"); + if (string.IsNullOrWhiteSpace(weather)) + { + weather = "balmy,rainy,stormy"; + } + + var weatherChoices = weather.Split(","); + var selectedWeatherIndex = Random.Shared.Next(0, weatherChoices.Length); + + return $"The weather in {city} is {weatherChoices[selectedWeatherIndex]}."; + } +} diff --git a/docs/ai/toc.yml b/docs/ai/toc.yml index 1f73847aee337..6f87e87e4daf3 100644 --- a/docs/ai/toc.yml +++ b/docs/ai/toc.yml @@ -33,7 +33,7 @@ items: href: quickstarts/create-assistant.md - name: Get started using the AI app templates href: quickstarts/ai-templates.md - - name: Build a minimal MCP server + - name: Build a minimal MCP server and publish to NuGet href: quickstarts/build-mcp-server.md - name: Build a minimal MCP client href: quickstarts/build-mcp-client.md