Skip to content

Commit afd4611

Browse files
committed
add MCP tutorial
1 parent 8682305 commit afd4611

File tree

7 files changed

+233
-1
lines changed

7 files changed

+233
-1
lines changed
222 KB
Loading
40.5 KB
Loading
96 KB
Loading
39.2 KB
Loading
28.5 KB
Loading

articles/app-service/toc.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,12 @@ items:
5959
href: tutorial-ai-openai-search-dotnet.md
6060
- name: RAG Azure OpenAI and Azure SQL
6161
href: deploy-intelligent-apps-dotnet-to-azure-sql.md
62-
- name: Integrate into Foundry Agent with OpenAPI
62+
- name: AI Foundry Agent with OpenAPI app
6363
href: tutorial-ai-integrate-azure-ai-agent-openapi-dotnet.md
6464
- name: Invoke OpenAPI app from Azure AI Agent
6565
href: invoke-openapi-web-app-from-azure-ai-agent-service.md
66+
- name: Add MCP server to app
67+
href: tutorial-ai-mcp-server-dotnet.md
6668
- name: Chatbot with local SLM
6769
href: tutorial-ai-slm-dotnet.md
6870

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
---
2+
title: Web app as MCP server in GitHub Copilot Chat agent mode
3+
description: Empower GitHub Copilot Chat with your existing web apps by integrating their capabilities as Model Context Protocol servers, enabling Copilot Chat to perform real-world tasks.
4+
author: cephalin
5+
ms.author: cephalin
6+
ms.date: 06/17/2025
7+
ms.topic: tutorial
8+
ms.custom:
9+
- devx-track-dotnet
10+
ms.collection: ce-skilling-ai-copilot
11+
---
12+
13+
# Integrate an App Service app as an MCP Server for GitHub Copilot Chat (.NET)
14+
15+
In this tutorial, you'll learn how to expose your 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.
16+
17+
:::image type="content" source="media/tutorial-ai-mcp-server-dotnet/mcp-call-intro.png" alt-text="Screenshot showing GitHub Copilot calling Todos MCP server hosted in Azure App Service.":::
18+
19+
If your web application already has useful features, like shopping, hotel booking, or data management, it's easy to make those capabilities available for:
20+
21+
- Any [application that supports MCP integration](https://modelcontextprotocol.io/clients), such as GitHub Copilot Chat agent mode in Visual Studio Code or in GitHub Codespaces.
22+
- A custom agent that accesses remote tools by using an [MCP client](https://modelcontextprotocol.io/quickstart/client#c).
23+
24+
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.
25+
26+
> [!div class="checklist"]
27+
> * Add an MCP server to your web app.
28+
> * Test the MCP server locally in GitHub Copilot Chat agent mode.
29+
> * Deploy the MCP server to Azure App Service and connect to it in GitHub Copilot Chat.
30+
31+
## Prerequisites
32+
33+
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](tutorial-dotnetcore-sqldb-app.md).
34+
35+
At a minimum, open the [sample application](https://github.com/Azure-Samples/msdocs-app-service-sqldb-dotnetcore) in GitHub Codespaces and deploy the app by running `azd up`.
36+
37+
## Add MCP server to your web app
38+
39+
1. In the codespace terminal, add the NuGet `ModelContextProtocol.AspNetCore` package to your project:
40+
41+
```dotnetcli
42+
dotnet add package ModelContextProtocol.AspNetCore --prerelease
43+
```
44+
45+
1. Create an McpServer folder, and create a *TodosMcpTool.cs* in it with the following code.
46+
47+
```csharp
48+
using DotNetCoreSqlDb.Data;
49+
using DotNetCoreSqlDb.Models;
50+
using Microsoft.EntityFrameworkCore;
51+
using System.ComponentModel;
52+
using ModelContextProtocol.Server;
53+
54+
namespace DotNetCoreSqlDb.McpServer
55+
{
56+
[McpServerToolType]
57+
public class TodosMcpTool
58+
{
59+
private readonly MyDatabaseContext _db;
60+
61+
public TodosMcpTool(MyDatabaseContext db)
62+
{
63+
_db = db;
64+
}
65+
66+
[McpServerTool, Description("Creates a new todo with a description and creation date.")]
67+
public async Task<string> CreateTodoAsync(
68+
[Description("Description of the todo")] string description,
69+
[Description("Creation date of the todo")] DateTime createdDate)
70+
{
71+
var todo = new Todo
72+
{
73+
Description = description,
74+
CreatedDate = createdDate
75+
};
76+
_db.Todo.Add(todo);
77+
await _db.SaveChangesAsync();
78+
return $"Todo created: {todo.Description} (Id: {todo.ID})";
79+
}
80+
81+
[McpServerTool, Description("Reads all todos, or a single todo if an id is provided.")]
82+
public async Task<List<Todo>> ReadTodosAsync(
83+
[Description("Id of the todo to read (optional)")] string? id = null)
84+
{
85+
if (!string.IsNullOrWhiteSpace(id) && int.TryParse(id, out int todoId))
86+
{
87+
var todo = await _db.Todo.FindAsync(todoId);
88+
if (todo == null) return new List<Todo>();
89+
return new List<Todo> { todo };
90+
}
91+
var todos = await _db.Todo.OrderBy(t => t.ID).ToListAsync();
92+
return todos;
93+
}
94+
95+
[McpServerTool, Description("Updates the specified todo fields by id.")]
96+
public async Task<string> UpdateTodoAsync(
97+
[Description("Id of the todo to update")] string id,
98+
[Description("New description (optional)")] string? description = null,
99+
[Description("New creation date (optional)")] DateTime? createdDate = null)
100+
{
101+
if (!int.TryParse(id, out int todoId))
102+
return "Invalid todo id.";
103+
var todo = await _db.Todo.FindAsync(todoId);
104+
if (todo == null) return $"Todo with Id {todoId} not found.";
105+
if (!string.IsNullOrWhiteSpace(description)) todo.Description = description;
106+
if (createdDate.HasValue) todo.CreatedDate = createdDate.Value;
107+
await _db.SaveChangesAsync();
108+
return $"Todo {todo.ID} updated.";
109+
}
110+
111+
[McpServerTool, Description("Deletes a todo by id.")]
112+
public async Task<string> DeleteTodoAsync(
113+
[Description("Id of the todo to delete")] string id)
114+
{
115+
if (!int.TryParse(id, out int todoId))
116+
return "Invalid todo id.";
117+
var todo = await _db.Todo.FindAsync(todoId);
118+
if (todo == null) return $"Todo with Id {todoId} not found.";
119+
_db.Todo.Remove(todo);
120+
await _db.SaveChangesAsync();
121+
return $"Todo {todo.ID} deleted.";
122+
}
123+
}
124+
}
125+
```
126+
127+
The code above makes tools available for the MCP server by using the following specific attributes:
128+
129+
- `[McpServerToolType]`: Marks the `TodosMcpTool` 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.
130+
- `[McpServerTool]`: Marks a method as a callable action for the MCP server.
131+
- `[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.
132+
133+
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 from `TodosController` and from `TodosMcpTool`.
134+
135+
1. In *Program.cs*, register the MCP server service and the CORS service.
136+
137+
```csharp
138+
builder.Services.AddMcpServer()
139+
.WithHttpTransport() // With streamable HTTP
140+
.WithToolsFromAssembly(); // Add all classes marked with [McpServerToolType]
141+
142+
builder.Services.AddCors(options =>
143+
{
144+
options.AddDefaultPolicy(policy =>
145+
{
146+
policy.AllowAnyOrigin()
147+
.AllowAnyHeader()
148+
.AllowAnyMethod();
149+
});
150+
});
151+
```
152+
153+
When you [use streamable HTTP with the MCP server](https://mcp-framework.com/docs/Transports/http-stream-transport/), 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).
154+
155+
1. In *Program.cs*, enable the MCP and CORS middleware.
156+
157+
```csharp
158+
app.MapMcp("/api/mcp");
159+
app.UseCors();
160+
```
161+
162+
This code sets your MCP server endpoint to `<url>/api/mcp`.
163+
164+
## Test the MCP server locally
165+
166+
1. In the codespace terminal, run the application with `dotnet run`.
167+
168+
1. Select **Open in Browser**, then add a tasks.
169+
170+
Leave `dotnet run` running. Your MCP server is running at `http://localhost:5093/api/mcp` now.
171+
172+
1. Back in the codespace, open Copilot Chat, then select **Agent** mode in the prompt box.
173+
174+
1. Select the **Tools** button, then select **Add More Tools...** in the dropdown.
175+
176+
:::image type="content" source="media/tutorial-ai-mcp-server-dotnet/add-mcp-tool.png" alt-text="Screenshot showing how to add an MCP server in GitHub Copilot Chat agent mode.":::
177+
178+
1. Select **Add MCP Server**.
179+
180+
1. Select **HTTP (HTTP or Server-Sent Events)**.
181+
182+
1. In **Enter Server URL**, type *http://localhost:5093/api/mcp*.
183+
184+
1. In **Enter Server ID**, type *todos-mcp* or any name you like.
185+
186+
1. Select **Workspace Settings**.
187+
188+
1. In a new Copilot Chat window, type something like *"Show me the todos."*
189+
190+
1. By default, GitHub Copilot shows you a security confirmation when you invoke an MCP server. Select **Continue**.
191+
192+
:::image type="content" source="media/tutorial-ai-mcp-server-dotnet/mcp-call-confirmation.png" alt-text="Screenshot showing the default security message from an MCP invocation in GitHub Copilot Chat.":::
193+
194+
You should now see a response that indicates that the MCP tool call is successful.
195+
196+
:::image type="content" source="media/tutorial-ai-mcp-server-dotnet/mcp-call-success.png" alt-text="Screenshot showing that the response from the MCP tool call in the GitHub Copilot Chat window.":::
197+
198+
## Deploy your MCP server to App Service
199+
200+
1. Back in the codespace terminal, deploy your changes by committing your changes (GitHub Actions method) or run `azd up` (Azure Developer CLI method).
201+
202+
1. In the AZD output, find the URL of your app. The URL looks like this in the AZD output:
203+
204+
<pre>
205+
Deploying services (azd deploy)
206+
207+
(✓) Done: Deploying service web
208+
- Endpoint: &lt;app-url>
209+
</pre>
210+
211+
1. Once `azd up` finishes, open *.vscode/mcp.json*. Change the URL to `<app-url>/api/mcp`.
212+
213+
1. Above your modified MCP server configuration, select **Start**.
214+
215+
:::image type="content" source="media/tutorial-ai-mcp-server-dotnet/mcp-server-start.png" alt-text="Screenshot showing how to manually start an MCP server from the local mcp.json file.":::
216+
217+
1. Start a new GitHub Copilot Chat window. You should be able to view, create, update, and delete tasks in the Copilot agent.
218+
219+
> [!TIP]
220+
> You can deactivate the confirmation message by adding a *.vscode/settings.json* with the following JSON:
221+
>
222+
> ```json
223+
> {
224+
> "chat.tools.autoApprove": true
225+
> }
226+
> ```
227+
228+
## More resources
229+
230+
[Integrate AI into your Azure App Service applications](overview-ai-integration.md)

0 commit comments

Comments
 (0)