Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
In this tutorial, you'll learn how to expose an ASP.NET Core app's functionality through Model Context Protocol (MCP), add it as a tool to GitHub Copilot, and interact with your app using natural language in Copilot Chat agent mode.
If your web application already has useful features, like shopping, hotel booking, or data management, it's easy to make those capabilities available for:
- Any application that supports MCP integration, such as GitHub Copilot Chat agent mode in Visual Studio Code or in GitHub Codespaces.
- A custom agent that accesses remote tools by using an MCP client.
By adding an MCP server to your web app, you enable an agent to understand and use your app's capabilities when it responds to user prompts. This means anything your app can do, the agent can do too.
- Add an MCP server to your web app.
- Test the MCP server locally in GitHub Copilot Chat agent mode.
- Deploy the MCP server to Azure App Service and connect to it in GitHub Copilot Chat.
Prerequisites
This tutorial assumes you're working with the sample used in Tutorial: Deploy an ASP.NET Core and Azure SQL Database app to Azure App Service.
At a minimum, open the sample application in GitHub Codespaces and deploy the app by running azd up
.
Add MCP server to your web app
In the codespace terminal, add the NuGet
ModelContextProtocol.AspNetCore
package to your project:dotnet add package ModelContextProtocol.AspNetCore --prerelease
Create an McpServer folder, and create a TodosMcpTool.cs in it with the following code.
using DotNetCoreSqlDb.Data; using DotNetCoreSqlDb.Models; using Microsoft.EntityFrameworkCore; using System.ComponentModel; using ModelContextProtocol.Server; namespace DotNetCoreSqlDb.McpServer { [McpServerToolType] public class TodosMcpTool { private readonly MyDatabaseContext _db; public TodosMcpTool(MyDatabaseContext db) { _db = db; } [McpServerTool, Description("Creates a new todo with a description and creation date.")] public async Task<string> CreateTodoAsync( [Description("Description of the todo")] string description, [Description("Creation date of the todo")] DateTime createdDate) { var todo = new Todo { Description = description, CreatedDate = createdDate }; _db.Todo.Add(todo); await _db.SaveChangesAsync(); return $"Todo created: {todo.Description} (Id: {todo.ID})"; } [McpServerTool, Description("Reads all todos, or a single todo if an id is provided.")] public async Task<List<Todo>> ReadTodosAsync( [Description("Id of the todo to read (optional)")] string? id = null) { if (!string.IsNullOrWhiteSpace(id) && int.TryParse(id, out int todoId)) { var todo = await _db.Todo.FindAsync(todoId); if (todo == null) return new List<Todo>(); return new List<Todo> { todo }; } var todos = await _db.Todo.OrderBy(t => t.ID).ToListAsync(); return todos; } [McpServerTool, Description("Updates the specified todo fields by id.")] public async Task<string> UpdateTodoAsync( [Description("Id of the todo to update")] string id, [Description("New description (optional)")] string? description = null, [Description("New creation date (optional)")] DateTime? createdDate = null) { if (!int.TryParse(id, out int todoId)) return "Invalid todo id."; var todo = await _db.Todo.FindAsync(todoId); if (todo == null) return $"Todo with Id {todoId} not found."; if (!string.IsNullOrWhiteSpace(description)) todo.Description = description; if (createdDate.HasValue) todo.CreatedDate = createdDate.Value; await _db.SaveChangesAsync(); return $"Todo {todo.ID} updated."; } [McpServerTool, Description("Deletes a todo by id.")] public async Task<string> DeleteTodoAsync( [Description("Id of the todo to delete")] string id) { if (!int.TryParse(id, out int todoId)) return "Invalid todo id."; var todo = await _db.Todo.FindAsync(todoId); if (todo == null) return $"Todo with Id {todoId} not found."; _db.Todo.Remove(todo); await _db.SaveChangesAsync(); return $"Todo {todo.ID} deleted."; } } }
The code above makes tools available for the MCP server by using the following specific attributes:
[McpServerToolType]
: Marks theTodosMcpTool
class as an MCP server tool type. It signals to the MCP framework that this class contains methods that should be exposed as callable tools.[McpServerTool]
: Marks a method as a callable action for the MCP server.[Description]
: These provide human-readable descriptions for methods and parameters. It helps the calling agent to understand how to use the actions and their parameters.
This code is duplicating the functionality of the existing
TodosController
, which is unnecessary, but you'll keep it for simplicity. A best practice would be to move the app logic to a service class, then call the service methods both fromTodosController
and fromTodosMcpTool
.In Program.cs, register the MCP server service and the CORS service.
builder.Services.AddMcpServer() .WithHttpTransport() // With streamable HTTP .WithToolsFromAssembly(); // Add all classes marked with [McpServerToolType] builder.Services.AddCors(options => { options.AddDefaultPolicy(policy => { policy.AllowAnyOrigin() .AllowAnyHeader() .AllowAnyMethod(); }); });
When you use streamable HTTP with the MCP server, you need to enable Cross-Origin Resource Sharing (CORS) if you want to test it with client browser tools or GitHub Copilot (both in Visual Studio Code and in GitHub Codespaces).
In Program.cs, enable the MCP and CORS middleware.
app.MapMcp("/api/mcp"); app.UseCors();
This code sets your MCP server endpoint to
<url>/api/mcp
.
Test the MCP server locally
In the codespace terminal, run the application with
dotnet run
.Select Open in Browser, then add a task.
Leave
dotnet run
running. Your MCP server is running athttp://localhost:5093/api/mcp
now.Back in the codespace, open Copilot Chat, then select Agent mode in the prompt box.
Select the Tools button, then select Add More Tools... in the dropdown.
Select Add MCP Server.
Select HTTP (HTTP or Server-Sent Events).
In Enter Server URL, type http://localhost:5093/api/mcp.
In Enter Server ID, type todos-mcp or any name you like.
Select Workspace Settings.
In a new Copilot Chat window, type something like "Show me the todos."
By default, GitHub Copilot shows you a security confirmation when you invoke an MCP server. Select Continue.
You should now see a response that indicates that the MCP tool call is successful.
Deploy your MCP server to App Service
Back in the codespace terminal, deploy your changes by committing your changes (GitHub Actions method) or run
azd up
(Azure Developer CLI method).In the AZD output, find the URL of your app. The URL looks like this in the AZD output:
Deploying services (azd deploy) (✓) Done: Deploying service web - Endpoint: <app-url>
Once
azd up
finishes, open .vscode/mcp.json. Change the URL to<app-url>/api/mcp
.Above your modified MCP server configuration, select Start.
Start a new GitHub Copilot Chat window. You should be able to view, create, update, and delete tasks in the Copilot agent.
Security best practices
When your MCP server is called by an agent powered by large language models (LLM), be aware of prompt injection attacks. Consider the following security best practices:
- Authentication and Authorization: Protect your MCP endpoints in App Service behind Azure API Management with Microsoft Entra ID and ensure only authorized users or agents can access the tools.
- Input Validation and Sanitization: The example code in this tutorial omits input validation and sanitization for simplicity and clarity. In production scenarios, always implement proper validation and sanitization to protect your application. For ASP.NET Core, see Model validation in ASP.NET Core.
- HTTPS: The sample relies on Azure App Service, which enforces HTTPS by default and provides free TLS/SSL certificates to encrypt data in transit.
- Least Privilege Principle: Expose only the necessary tools and data required for your use case. Avoid exposing sensitive operations unless necessary.
- Rate Limiting and Throttling: Use API Management or custom middleware to prevent abuse and denial-of-service attacks.
- Logging and Monitoring: Log access and usage of MCP endpoints for auditing and anomaly detection. Monitor for suspicious activity.
- CORS Configuration: Restrict cross-origin requests to trusted domains if your MCP server is accessed from browsers. For more information, see Enable CORS.
- Regular Updates: Keep your dependencies up to date to mitigate known vulnerabilities.