diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index 280cdd7743..07fbcf93b4 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -75,8 +75,10 @@ + + diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index 2bf90eda9b..cd28a9e723 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -53,6 +53,10 @@ + + + + diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Agent_MCP_Server.csproj b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Agent_MCP_Server.csproj new file mode 100644 index 0000000000..759eaae572 --- /dev/null +++ b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Agent_MCP_Server.csproj @@ -0,0 +1,25 @@ + + + + Exe + net9.0 + 12 + + enable + disable + + + + + + + + + + + + + + + + diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Program.cs b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Program.cs new file mode 100644 index 0000000000..8faffd1339 --- /dev/null +++ b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Program.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All rights reserved. + +// This sample shows how to create and use a simple AI agent with tools from an MCP Server. + +using System; +using System.Linq; +using Azure.AI.OpenAI; +using Azure.Identity; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI.Agents; +using ModelContextProtocol.Client; +using OpenAI; + +var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); +var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; + +// Create an MCPClient for the GitHub server +await using var mcpClient = await McpClientFactory.CreateAsync(new StdioClientTransport(new() +{ + Name = "MCPServer", + Command = "npx", + Arguments = ["-y", "--verbose", "@modelcontextprotocol/server-github"], +})); + +// Retrieve the list of tools available on the GitHub server +var mcpTools = await mcpClient.ListToolsAsync().ConfigureAwait(false); + +AIAgent agent = new AzureOpenAIClient( + new Uri(endpoint), + new AzureCliCredential()) + .GetChatClient(deploymentName) + .CreateAIAgent(instructions: "You answer questions related to GitHub repositories only.", tools: [.. mcpTools.Cast()]); + +// Invoke the agent and output the text result. +Console.WriteLine(await agent.RunAsync("Summarize the last four commits to the microsoft/semantic-kernel repository?")); diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/README.md b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/README.md new file mode 100644 index 0000000000..f0996dc1fd --- /dev/null +++ b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/README.md @@ -0,0 +1,31 @@ +# Model Context Protocol Sample + +This example demonstrates how to use tools from a Model Context Protocol server with Agent Framework. + +MCP is an open protocol that standardizes how applications provide context to LLMs. + +For information on Model Context Protocol (MCP) please refer to the [documentation](https://modelcontextprotocol.io/introduction). + +The sample shows: + +1. How to connect to an MCP Server +1. Retrieve the list of tools the MCP Server makes available +1. Convert the MCP tools to `AIFunction`'s so they can be added to an agent +1. Invoke the tools from an agent using function calling + +## Configuring Environment Variables + +Set the following environment variables: + +```powershell +$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Replace with your Azure OpenAI resource endpoint +$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini +``` + +## Setup and Running + +Run the ModelContextProtocolPluginAuth sample + +```bash +dotnet run +``` diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/Agent_MCP_Server_Auth.csproj b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/Agent_MCP_Server_Auth.csproj new file mode 100644 index 0000000000..d26676a576 --- /dev/null +++ b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/Agent_MCP_Server_Auth.csproj @@ -0,0 +1,27 @@ + + + + Exe + net9.0 + 12 + + enable + disable + + + + + + + + + + + + + + + + + + diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/Program.cs b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/Program.cs new file mode 100644 index 0000000000..26e2ae22f1 --- /dev/null +++ b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/Program.cs @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft. All rights reserved. + +// This sample shows how to create and use a simple AI agent with tools from an MCP Server that requires authentication. + +using System; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using Azure.AI.OpenAI; +using Azure.Identity; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI.Agents; +using Microsoft.Extensions.Logging; +using ModelContextProtocol.Client; +using OpenAI; + +var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); +var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; + +// We can customize a shared HttpClient with a custom handler if desired +using var sharedHandler = new SocketsHttpHandler +{ + PooledConnectionLifetime = TimeSpan.FromMinutes(2), + PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1) +}; +using var httpClient = new HttpClient(sharedHandler); + +var consoleLoggerFactory = LoggerFactory.Create(builder => +{ + builder.AddConsole(); +}); + +// Create SSE client transport for the MCP server +var serverUrl = "http://localhost:7071/"; +var transport = new SseClientTransport(new() +{ + Endpoint = new Uri(serverUrl), + Name = "Secure Weather Client", + OAuth = new() + { + ClientName = "ProtectedMcpClient", + RedirectUri = new Uri("http://localhost:1179/callback"), + AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync, + } +}, httpClient, consoleLoggerFactory); + +// Create an MCPClient for the protected MCP server +await using var mcpClient = await McpClientFactory.CreateAsync(transport, loggerFactory: consoleLoggerFactory); + +// Retrieve the list of tools available on the GitHub server +var mcpTools = await mcpClient.ListToolsAsync().ConfigureAwait(false); + +AIAgent agent = new AzureOpenAIClient( + new Uri(endpoint), + new AzureCliCredential()) + .GetChatClient(deploymentName) + .CreateAIAgent(instructions: "You answer questions related to the weather.", tools: [.. mcpTools.Select(mcpTool => (AITool)mcpTool)]); + +// Invoke the agent and output the text result. +Console.WriteLine(await agent.RunAsync("Get current weather alerts for New York?")); + +// Handles the OAuth authorization URL by starting a local HTTP server and opening a browser. +// This implementation demonstrates how SDK consumers can provide their own authorization flow. +static async Task HandleAuthorizationUrlAsync(Uri authorizationUrl, Uri redirectUri, CancellationToken cancellationToken) +{ + Console.WriteLine("Starting OAuth authorization flow..."); + Console.WriteLine($"Opening browser to: {authorizationUrl}"); + + var listenerPrefix = redirectUri.GetLeftPart(UriPartial.Authority); + if (!listenerPrefix.EndsWith("/", StringComparison.InvariantCultureIgnoreCase)) + { + listenerPrefix += "/"; + } + + using var listener = new HttpListener(); + listener.Prefixes.Add(listenerPrefix); + + try + { + listener.Start(); + Console.WriteLine($"Listening for OAuth callback on: {listenerPrefix}"); + + OpenBrowser(authorizationUrl); + + var context = await listener.GetContextAsync(); + var query = HttpUtility.ParseQueryString(context.Request.Url?.Query ?? string.Empty); + var code = query["code"]; + var error = query["error"]; + + string responseHtml = "

Authentication complete

You can close this window now.

"; + byte[] buffer = Encoding.UTF8.GetBytes(responseHtml); + context.Response.ContentLength64 = buffer.Length; + context.Response.ContentType = "text/html"; + context.Response.OutputStream.Write(buffer, 0, buffer.Length); + context.Response.Close(); + + if (!string.IsNullOrEmpty(error)) + { + Console.WriteLine($"Auth error: {error}"); + return null; + } + + if (string.IsNullOrEmpty(code)) + { + Console.WriteLine("No authorization code received"); + return null; + } + + Console.WriteLine("Authorization code received successfully."); + return code; + } + catch (Exception ex) + { + Console.WriteLine($"Error getting auth code: {ex.Message}"); + return null; + } + finally + { + if (listener.IsListening) + { + listener.Stop(); + } + } +} + +// Opens the specified URL in the default browser. +static void OpenBrowser(Uri url) +{ + try + { + var psi = new ProcessStartInfo + { + FileName = url.ToString(), + UseShellExecute = true + }; + Process.Start(psi); + } + catch (Exception ex) + { + Console.WriteLine($"Error opening browser. {ex.Message}"); + Console.WriteLine($"Please manually open this URL: {url}"); + } +} diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/README.md b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/README.md new file mode 100644 index 0000000000..ae88df95ee --- /dev/null +++ b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/README.md @@ -0,0 +1,125 @@ +# Model Context Protocol Sample + +This example demonstrates how to use tools from a protected Model Context Protocol server with Agent Framework. + +MCP is an open protocol that standardizes how applications provide context to LLMs. + +For information on Model Context Protocol (MCP) please refer to the [documentation](https://modelcontextprotocol.io/introduction). + +The sample shows: + +1. How to connect to a protected MCP Server using OAuth 2.0 authentication +1. How to implement a custom OAuth authorization flow with browser-based authentication +1. Retrieve the list of tools the MCP Server makes available +1. Convert the MCP tools to `AIFunction`'s so they can be added to an agent +1. Invoke the tools from an agent using function calling + +## Installing Prerequisites + +- A self-signed certificate to enable HTTPS use in development, see [dotnet dev-certs](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-dev-certs) +- .NET 9.0 or later +- A running TestOAuthServer (for OAuth authentication), see [Start the Test OAuth Server](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/ProtectedMcpClient#step-1-start-the-test-oauth-server) +- A running ProtectedMCPServer (for MCP services), see [Start the Protected MCP Server](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/ProtectedMcpClient#step-2-start-the-protected-mcp-server) + +## Configuring Environment Variables + +Set the following environment variables: + +```powershell +$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Replace with your Azure OpenAI resource endpoint +$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini +``` + +## Setup and Running + +### Step 1: Start the Test OAuth Server + +First, you need to start the TestOAuthServer which provides OAuth authentication: + +```bash +cd \tests\ModelContextProtocol.TestOAuthServer +dotnet run --framework net9.0 +``` + +The OAuth server will start at `https://localhost:7029` + +### Step 2: Start the Protected MCP Server + +Next, start the ProtectedMCPServer which provides the weather tools: + +```bash +cd \samples\ProtectedMCPServer +dotnet run +``` + +The protected server will start at `http://localhost:7071` + +### Step 3: Run the ModelContextProtocolPluginAuth sample + +Finally, run this client: + +```bash +dotnet run +``` + +## What Happens + +1. The client attempts to connect to the protected MCP server at `http://localhost:7071` +2. The server responds with OAuth metadata indicating authentication is required +3. The client initiates OAuth 2.0 authorization code flow: + - Opens a browser to the authorization URL at the OAuth server + - Starts a local HTTP listener on `http://localhost:1179/callback` to receive the authorization code + - Exchanges the authorization code for an access token +4. The client uses the access token to authenticate with the MCP server +5. The client lists available tools and calls the `GetAlerts` tool for New York state + +The following diagram outlines an example OAuth flow: + +```mermaid +sequenceDiagram + participant Client as Client + participant Server as MCP Server (Resource Server) + participant AuthServer as Authorization Server + + Client->>Server: MCP request without access token + Server-->>Client: HTTP 401 Unauthorized with WWW-Authenticate header + Note over Client: Analyze and delegate tasks + Client->>Server: GET /.well-known/oauth-protected-resource + Server-->>Client: Resource metadata with authorization server URL + Note over Client: Validate RS metadata, build AS metadata URL + Client->>AuthServer: GET /.well-known/oauth-authorization-server + AuthServer-->>Client: Authorization server metadata + Note over Client,AuthServer: OAuth 2.0 authorization flow happens here + Client->>AuthServer: Token request + AuthServer-->>Client: Access token + Client->>Server: MCP request with access token + Server-->>Client: MCP response + Note over Client,Server: MCP communication continues with valid token +``` + +## OAuth Configuration + +The client is configured with: +- **Client ID**: `demo-client` +- **Client Secret**: `demo-secret` +- **Redirect URI**: `http://localhost:1179/callback` +- **OAuth Server**: `https://localhost:7029` +- **Protected Resource**: `http://localhost:7071` + +## Available Tools + +Once authenticated, the client can access weather tools including: +- **GetAlerts**: Get weather alerts for a US state +- **GetForecast**: Get weather forecast for a location (latitude/longitude) + +## Troubleshooting + +- Ensure the ASP.NET Core dev certificate is trusted. + ``` + dotnet dev-certs https --clean + dotnet dev-certs https --trust + ``` +- Ensure all three services are running in the correct order +- Check that ports 7029, 7071, and 1179 are available +- If the browser doesn't open automatically, copy the authorization URL from the console and open it manually +- Make sure to allow the OAuth server's self-signed certificate in your browser \ No newline at end of file diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/README.md b/dotnet/samples/GettingStarted/ModelContextProtocol/README.md new file mode 100644 index 0000000000..be84bff51f --- /dev/null +++ b/dotnet/samples/GettingStarted/ModelContextProtocol/README.md @@ -0,0 +1,64 @@ +# Getting started with Model Content Protocol + +The getting started with Model Content Protocol samples demonstrate how to use MCP Server tools from an agent. + +## Getting started with agents prerequisites + +Before you begin, ensure you have the following prerequisites: + +- .NET 9.0 SDK or later +- Azure OpenAI service endpoint and deployment configured +- Azure CLI installed and authenticated (for Azure credential authentication) +- User has the `Cognitive Services OpenAI Contributor` role for the Azure OpenAI resource. + +**Note**: These samples use Azure OpenAI models. For more information, see [how to deploy Azure OpenAI models with Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/deploy-models-openai). + +**Note**: These samples use Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Azure OpenAI resource and have the `Cognitive Services OpenAI Contributor` role. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively). + +## Samples + +|Sample|Description| +|---|---| +|[Agent with MCP server tools](./Agent_MCP_Server/)|This sample demonstrates how to use MCP server tools with a simple agent| +|[Agent with MCP server tools and authorization](./Agent_MCP_Server_Auth/)|This sample demonstrates how to use MCP Server tools from a protected MCP server with a simple agent| + +## Running the samples from the console + +To run the samples, navigate to the desired sample directory, e.g. + +```powershell +cd Agents_Step01_Running +``` + +Set the following environment variables: + +```powershell +$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Replace with your Azure OpenAI resource endpoint +$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini +``` + +If the variables are not set, you will be prompted for the values when running the samples. + +Execute the following command to build the sample: + +```powershell +dotnet build +``` + +Execute the following command to run the sample: + +```powershell +dotnet run --no-build +``` + +Or just build and run in one step: + +```powershell +dotnet run +``` + +## Running the samples from Visual Studio + +Open the solution in Visual Studio and set the desired sample project as the startup project. Then, run the project using the built-in debugger or by pressing `F5`. + +You will be prompted for any required environment variables if they are not already set. diff --git a/dotnet/samples/GettingStarted/README.md b/dotnet/samples/GettingStarted/README.md index e95b96c923..dacf0b92cd 100644 --- a/dotnet/samples/GettingStarted/README.md +++ b/dotnet/samples/GettingStarted/README.md @@ -12,3 +12,4 @@ of the agent framework. |[Agent Open Telemetry](./AgentOpenTelemetry/README.md)|Getting started with OpenTelemetry for agents| |[Agent With OpenAI exchange types](./AgentWithOpenAI/README.md)|Using OpenAI exchange types with agents| |[Workflow](./Workflow/README.md)|Getting started with Workflow| +|[Model Context Protocol](./ModelContextProtocol/README.md)|Getting started with Model Context Protocol|