mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
* dotnet: refresh Foundry sample guidance Carry forward the still-relevant sample guidance and Foundry-specific documentation fixes from the old stacked sample migration work, adapted to the current repo layout and policy. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * dotnet: rename Foundry sample env vars Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * dotnet: remove persistent provider sample Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * dotnet: drop SAMPLE_GUIDELINES.md from this PR Defer the guidelines doc and its cross-link to a follow-on PR to avoid broken-link failures in CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * dotnet: add DefaultAzureCredential warning to remaining samples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * dotnet: address PR review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
150 lines
6.7 KiB
C#
150 lines
6.7 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
// Foundry Toolbox via MCP (Streamable HTTP).
|
|
//
|
|
// Point an `McpClient` at a Foundry Toolbox's MCP endpoint. The agent
|
|
// discovers the toolbox's tools at runtime and invokes them locally.
|
|
|
|
using System.ClientModel;
|
|
using System.ClientModel.Primitives;
|
|
using System.Net.Http.Headers;
|
|
using Azure.AI.Projects;
|
|
using Azure.AI.Projects.Agents;
|
|
using Azure.Core;
|
|
using Azure.Identity;
|
|
using Microsoft.Agents.AI;
|
|
using Microsoft.Extensions.AI;
|
|
using ModelContextProtocol.Client;
|
|
using OpenAI.Responses;
|
|
|
|
#pragma warning disable OPENAI001 // Experimental API
|
|
#pragma warning disable AAIP001 // AgentToolboxes is experimental
|
|
|
|
// Name of the toolbox to create and connect to.
|
|
const string ToolboxName = "research_toolbox";
|
|
const string Query = "What tools do you have access to?";
|
|
|
|
string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
|
|
?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
|
|
string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
|
|
|
TokenCredential credential = new DefaultAzureCredential();
|
|
|
|
// Comment out if the toolbox already exists in your Foundry project.
|
|
var toolboxEndpoint = await CreateSampleToolboxAsync(ToolboxName, endpoint, credential);
|
|
|
|
// Inject a fresh Azure AI bearer token on every MCP request.
|
|
using var httpClient = new HttpClient(new BearerTokenHandler(credential, "https://ai.azure.com/.default")
|
|
{
|
|
InnerHandler = new HttpClientHandler(),
|
|
});
|
|
|
|
Console.WriteLine($"Connecting to toolbox MCP endpoint: {toolboxEndpoint}");
|
|
|
|
await using McpClient mcpClient = await McpClient.CreateAsync(
|
|
new HttpClientTransport(
|
|
new HttpClientTransportOptions
|
|
{
|
|
Endpoint = new Uri(toolboxEndpoint),
|
|
Name = "foundry_toolbox",
|
|
TransportMode = HttpTransportMode.StreamableHttp,
|
|
AdditionalHeaders = new Dictionary<string, string>
|
|
{
|
|
["Foundry-Features"] = "Toolboxes=V1Preview",
|
|
},
|
|
},
|
|
httpClient));
|
|
|
|
IList<McpClientTool> mcpTools = await mcpClient.ListToolsAsync();
|
|
Console.WriteLine($"Toolbox MCP tools available: {string.Join(", ", mcpTools.Select(t => t.Name))}");
|
|
|
|
// 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.
|
|
AIProjectClient aiProjectClient = new(new Uri(endpoint), credential);
|
|
|
|
AIAgent agent = aiProjectClient.AsAIAgent(
|
|
model: deploymentName,
|
|
instructions: "You are a helpful assistant. Use the available toolbox tools to answer the user.",
|
|
name: "ToolboxMcpAgent",
|
|
tools: [.. mcpTools.Cast<AITool>()]);
|
|
|
|
Console.WriteLine($"\nUser: {Query}\n");
|
|
Console.WriteLine($"Assistant: {await agent.RunAsync(Query)}");
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helper: create (or replace) a sample toolbox so the sample runs end-to-end
|
|
// ---------------------------------------------------------------------------
|
|
static async Task<string> CreateSampleToolboxAsync(string name, string endpoint, TokenCredential credential)
|
|
{
|
|
// Toolboxes are normally configured in the Foundry portal or a deployment
|
|
// script, not the application itself. This helper exists so the sample can
|
|
// be run end-to-end without first setting a toolbox up by hand.
|
|
|
|
// The Foundry-Features header is currently required for toolbox CRUD operations.
|
|
var options = new AgentAdministrationClientOptions();
|
|
options.AddPolicy(new FoundryFeaturesPolicy("Toolboxes=V1Preview"), PipelinePosition.PerCall);
|
|
var adminClient = new AgentAdministrationClient(new Uri(endpoint), credential, options);
|
|
var toolboxClient = adminClient.GetAgentToolboxes();
|
|
|
|
// Delete existing toolbox if present (ignore 404).
|
|
try
|
|
{
|
|
await toolboxClient.DeleteToolboxAsync(name);
|
|
Console.WriteLine($"Deleted existing toolbox '{name}'");
|
|
}
|
|
catch (ClientResultException ex) when (ex.Status == 404)
|
|
{
|
|
// Toolbox does not exist — nothing to delete.
|
|
}
|
|
|
|
// Create a fresh version with a single MCP tool.
|
|
ProjectsAgentTool mcpTool = ProjectsAgentTool.AsProjectTool(ResponseTool.CreateMcpTool(
|
|
serverLabel: "api-specs",
|
|
serverUri: new Uri("https://gitmcp.io/Azure/azure-rest-api-specs"),
|
|
toolCallApprovalPolicy: new McpToolCallApprovalPolicy(GlobalMcpToolCallApprovalPolicy.NeverRequireApproval)));
|
|
|
|
ToolboxVersion created = (await toolboxClient.CreateToolboxVersionAsync(
|
|
name: name,
|
|
tools: [mcpTool],
|
|
description: "Sample toolbox with an MCP tool — created by Agent_Step25 sample.")).Value;
|
|
|
|
Console.WriteLine($"Created toolbox '{created.Name}' v{created.Version} ({created.Tools.Count} tool(s))");
|
|
return $"{endpoint}/toolboxes/{created.Name}/mcp?api-version=v{created.Version}";
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Pipeline policy: adds the Foundry-Features header for toolbox CRUD calls
|
|
// ---------------------------------------------------------------------------
|
|
internal sealed class FoundryFeaturesPolicy(string feature) : PipelinePolicy
|
|
{
|
|
private const string FeatureHeader = "Foundry-Features";
|
|
|
|
public override void Process(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
|
|
{
|
|
message.Request.Headers.Add(FeatureHeader, feature);
|
|
ProcessNext(message, pipeline, currentIndex);
|
|
}
|
|
|
|
public override ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
|
|
{
|
|
message.Request.Headers.Add(FeatureHeader, feature);
|
|
return ProcessNextAsync(message, pipeline, currentIndex);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// DelegatingHandler: attaches a fresh Azure AI bearer token to every request
|
|
// ---------------------------------------------------------------------------
|
|
internal sealed class BearerTokenHandler(TokenCredential credential, string scope) : DelegatingHandler
|
|
{
|
|
private readonly TokenRequestContext _tokenContext = new([scope]);
|
|
|
|
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
|
{
|
|
AccessToken token = await credential.GetTokenAsync(this._tokenContext, cancellationToken).ConfigureAwait(false);
|
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Token);
|
|
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
}
|