chore: import upstream snapshot with attribution
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
This commit is contained in:
+24
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
|
||||
<PackageReference Include="ModelContextProtocol" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,145 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ModelContextProtocol.Client;
|
||||
using OpenAI.Chat;
|
||||
|
||||
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-5.4-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 HttpClientTransport(new()
|
||||
{
|
||||
Endpoint = new Uri(serverUrl),
|
||||
Name = "Secure Weather Client",
|
||||
OAuth = new()
|
||||
{
|
||||
DynamicClientRegistration = 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 McpClient.CreateAsync(transport, loggerFactory: consoleLoggerFactory);
|
||||
|
||||
// Retrieve the list of tools available on the GitHub server
|
||||
var mcpTools = await mcpClient.ListToolsAsync().ConfigureAwait(false);
|
||||
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.AsAIAgent(instructions: "You answer questions related to the weather.", tools: [.. mcpTools]);
|
||||
|
||||
// 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<string?> 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"];
|
||||
|
||||
const string ResponseHtml = "<html><body><h1>Authentication complete</h1><p>You can close this window now.</p></body></html>";
|
||||
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}");
|
||||
}
|
||||
}
|
||||
@@ -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 10.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-5.4-mini" # Optional, defaults to gpt-5.4-mini
|
||||
```
|
||||
|
||||
## Setup and Running
|
||||
|
||||
### Step 1: Start the Test OAuth Server
|
||||
|
||||
First, you need to start the TestOAuthServer which provides OAuth authentication:
|
||||
|
||||
```bash
|
||||
cd <MCP CSHARP-SDK>\tests\ModelContextProtocol.TestOAuthServer
|
||||
dotnet run --framework net10.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 <MCP CSHARP-SDK>\samples\ProtectedMCPServer
|
||||
dotnet run
|
||||
```
|
||||
|
||||
The protected server will start at `http://localhost:7071`
|
||||
|
||||
### Step 3: Run the Agent_MCP_Server_Auth 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
|
||||
Reference in New Issue
Block a user