mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Remove Foundry Toolbox server-side tools support (#5753)
* .NET: Remove Foundry Toolbox server-side tools support Mirrors the Python cleanup in microsoft/agent-framework#5671. Passing toolbox tools as server-side Responses tools is not the experience we want to support; the hosted-agent MCP toolbox path (HostedMcpToolboxAITool + FoundryToolboxService) remains the supported way to consume Foundry Toolboxes. Removed: - FoundryToolbox static class (GetToolboxVersionAsync / GetToolsAsync / ToAITools / SanitizeAndConvert) - AIProjectClient.GetToolboxToolsAsync extension - Agent_Step25_ToolboxServerSideTools sample (+ slnx entry) - FoundryToolboxTests, TestDataUtil, HttpHandlerAssert, and the toolbox JSON fixtures only those tests referenced - ToolboxHostedAgentTests and ToolboxHostedAgentFixture; the "toolbox" switch arm + CreateToolboxAgent helper in TestContainer; matching README scenario row and bootstrap script entry Kept (MCP path, unchanged): - HostedMcpToolboxAITool, FoundryAITool.CreateHostedMcpToolbox, FoundryAIToolExtensions.CreateHostedMcpToolbox(ToolboxRecord/Version) - FoundryToolboxService, AddFoundryToolboxes, marker injection in AgentFrameworkResponseHandler, InputConverter.ReadMcpToolboxMarkers - Hosted-Toolbox sample, McpToolbox* tests, FoundryToolboxServiceTests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Add Foundry Toolbox MCP sample (Agent_Step25_FoundryToolboxMcp) Adds a non-hosted-agent equivalent of the Python foundry_chat_client_with_toolbox.py sample. The agent connects to a Foundry Toolbox's MCP endpoint via Streamable HTTP, injects a fresh Azure AI bearer token on every request, and discovers the toolbox's tools at runtime via McpClient.ListToolsAsync. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Tighten Agent_Step25_FoundryToolboxMcp README/Program comments Drop 'non-hosted agent' framing from README (this sample isn't related to hosted agents) and remove narrative comparison to server-side tools from the Program.cs header comment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Drop python sample reference from Agent_Step25 README Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Drop incorrect .NET 10 prereq from Agent_Step25 README Toolboxes don't require .NET 10 (Microsoft.Agents.AI.Foundry targets net8.0+); the parent AgentsWithFoundry README already lists the sample SDK prereq. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Toolsets api-version in Agent_Step25 example endpoint Use 2025-05-01-preview to match FoundryToolboxOptions.ApiVersion. The placeholder 'v1' is not accepted by the Toolsets endpoint. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: alliscode <bentho@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot
alliscode
parent
0bbedc4fa2
commit
9199c84d42
+7
-3
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -6,12 +6,16 @@
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry.Hosting\Microsoft.Agents.AI.Foundry.Hosting.csproj" />
|
||||
<PackageReference Include="Azure.AI.Projects" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="ModelContextProtocol" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+70
-71
@@ -1,93 +1,80 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to load a Foundry toolbox and pass its tools as server-side
|
||||
// tools when creating an agent. The Foundry platform handles tool execution — the agent
|
||||
// process does not invoke tools locally.
|
||||
// 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
|
||||
#pragma warning disable CS8321 // Local functions may be commented-out alternatives
|
||||
|
||||
// Replace with your own Foundry toolbox name.
|
||||
// Must match the `<name>` segment of FOUNDRY_TOOLBOX_ENDPOINT.
|
||||
const string ToolboxName = "research_toolbox";
|
||||
// Used only by CombineToolboxes — swap in a second toolbox you own.
|
||||
const string SecondToolboxName = "analysis_toolbox";
|
||||
// Replace with any question that exercises the tools configured in your toolbox.
|
||||
const string Query = "Introduce yourself and briefly describe the tools you can use to help me.";
|
||||
const string Query = "What tools do you have access to?";
|
||||
|
||||
string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
|
||||
?? throw new InvalidOperationException("Set FOUNDRY_PROJECT_ENDPOINT to your Foundry project endpoint.");
|
||||
string model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
|
||||
string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
|
||||
?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
|
||||
string toolboxEndpoint = Environment.GetEnvironmentVariable("FOUNDRY_TOOLBOX_ENDPOINT")
|
||||
?? throw new InvalidOperationException(
|
||||
"FOUNDRY_TOOLBOX_ENDPOINT is not set. Example: " +
|
||||
"https://<account>.services.ai.azure.com/api/projects/<project>/toolsets/<name>/mcp?api-version=2025-05-01-preview");
|
||||
|
||||
TokenCredential credential = new DefaultAzureCredential();
|
||||
|
||||
// Comment out if the toolbox already exists in your Foundry project.
|
||||
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",
|
||||
},
|
||||
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.
|
||||
var projectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential());
|
||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), credential);
|
||||
|
||||
await Main(projectClient, model, endpoint);
|
||||
// await CombineToolboxes(projectClient, model, endpoint);
|
||||
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)}");
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main: single toolbox
|
||||
// Helper: create (or replace) a sample toolbox so the sample runs end-to-end
|
||||
// ---------------------------------------------------------------------------
|
||||
static async Task Main(AIProjectClient projectClient, string model, string endpoint)
|
||||
{
|
||||
Console.WriteLine("=== Foundry Toolbox Server-Side Tools Example ===");
|
||||
|
||||
// Comment out if the toolbox already exists in your Foundry project.
|
||||
await CreateSampleToolboxAsync(ToolboxName, endpoint);
|
||||
|
||||
// Omit the version to resolve the toolbox's current default version at runtime.
|
||||
var tools = await projectClient.GetToolboxToolsAsync(ToolboxName);
|
||||
|
||||
AIAgent agent = projectClient
|
||||
.AsAIAgent(
|
||||
model: model,
|
||||
instructions: "You are a research assistant. Use the available tools to answer questions.",
|
||||
tools: tools.ToList());
|
||||
|
||||
Console.WriteLine($"User: {Query}");
|
||||
Console.WriteLine($"Result: {await agent.RunAsync(Query)}\n");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Alternative: combine tools from multiple toolboxes
|
||||
// ---------------------------------------------------------------------------
|
||||
static async Task CombineToolboxes(AIProjectClient projectClient, string model, string endpoint)
|
||||
{
|
||||
Console.WriteLine("=== Combine Toolboxes Example ===");
|
||||
|
||||
// Comment out if the toolboxes already exist in your Foundry project.
|
||||
await CreateSampleToolboxAsync(ToolboxName, endpoint);
|
||||
await CreateSampleToolboxAsync(SecondToolboxName, endpoint);
|
||||
|
||||
var toolboxA = await projectClient.GetToolboxToolsAsync(ToolboxName);
|
||||
var toolboxB = await projectClient.GetToolboxToolsAsync(SecondToolboxName);
|
||||
|
||||
var allTools = toolboxA.Concat(toolboxB).ToList();
|
||||
|
||||
AIAgent agent = projectClient
|
||||
.AsAIAgent(
|
||||
model: model,
|
||||
instructions: "You are a research assistant. Use all available tools to answer questions.",
|
||||
tools: allTools);
|
||||
|
||||
Console.WriteLine($"User: {Query}");
|
||||
Console.WriteLine($"Combined-toolbox result: {await agent.RunAsync(Query)}\n");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: create (or replace) a sample toolbox so the sample works out-of-the-box
|
||||
// ---------------------------------------------------------------------------
|
||||
static async Task CreateSampleToolboxAsync(string name, string endpoint)
|
||||
static async Task 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
|
||||
@@ -96,10 +83,7 @@ static async Task CreateSampleToolboxAsync(string name, string endpoint)
|
||||
// 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),
|
||||
new DefaultAzureCredential(),
|
||||
options);
|
||||
var adminClient = new AgentAdministrationClient(new Uri(endpoint), credential, options);
|
||||
var toolboxClient = adminClient.GetAgentToolboxes();
|
||||
|
||||
// Delete existing toolbox if present (ignore 404).
|
||||
@@ -128,7 +112,7 @@ static async Task CreateSampleToolboxAsync(string name, string endpoint)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pipeline policy that adds the Foundry-Features header for toolbox CRUD
|
||||
// Pipeline policy: adds the Foundry-Features header for toolbox CRUD calls
|
||||
// ---------------------------------------------------------------------------
|
||||
internal sealed class FoundryFeaturesPolicy(string feature) : PipelinePolicy
|
||||
{
|
||||
@@ -146,3 +130,18 @@ internal sealed class FoundryFeaturesPolicy(string feature) : PipelinePolicy
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# Foundry Toolbox via MCP
|
||||
|
||||
This sample shows how to use a Foundry Toolbox by pointing an `McpClient` at the toolbox's MCP endpoint. The agent discovers the toolbox's tools at runtime and invokes them locally over MCP.
|
||||
|
||||
## What this sample demonstrates
|
||||
|
||||
- Connecting to a Foundry toolbox's MCP endpoint via Streamable HTTP transport
|
||||
- Injecting a fresh Azure AI bearer token (`https://ai.azure.com/.default`) on every MCP request
|
||||
- Passing the discovered MCP tools to `AIProjectClient.AsAIAgent(...)`
|
||||
- Optional helper to create (or replace) a sample toolbox in the project so the sample is runnable end-to-end
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A Microsoft Foundry project with a toolbox configured (or let the sample create one for you)
|
||||
- Azure CLI installed and authenticated (`az login`)
|
||||
|
||||
Set the following environment variables:
|
||||
|
||||
```powershell
|
||||
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
|
||||
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
|
||||
$env:FOUNDRY_TOOLBOX_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project/toolsets/research_toolbox/mcp?api-version=2025-05-01-preview"
|
||||
```
|
||||
|
||||
The `<name>` segment of `FOUNDRY_TOOLBOX_ENDPOINT` must match the `ToolboxName` constant in `Program.cs`.
|
||||
|
||||
## Run the sample
|
||||
|
||||
```powershell
|
||||
dotnet run
|
||||
```
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
# Agent_Step25_ToolboxServerSideTools
|
||||
|
||||
This sample demonstrates loading a named Foundry toolbox and passing its tools as
|
||||
**server-side tools** when creating an agent via `AsAIAgent()`.
|
||||
|
||||
When tools from a toolbox are passed this way, they are sent as tool definitions in
|
||||
the Responses API request. The Foundry platform handles tool execution — the agent
|
||||
process does not invoke tools locally.
|
||||
|
||||
This is the dotnet equivalent of the Python sample:
|
||||
`python/samples/02-agents/providers/foundry/foundry_chat_client_with_toolbox.py`
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A Microsoft Foundry project
|
||||
- `AZURE_AI_PROJECT_ENDPOINT` environment variable set to your Foundry project endpoint
|
||||
- `AZURE_AI_MODEL_DEPLOYMENT_NAME` environment variable set (defaults to `gpt-5.4-mini`)
|
||||
|
||||
The sample recreates the toolbox on each run, replacing any existing toolbox with
|
||||
the same name. Comment out the `CreateSampleToolboxAsync` call if you want to keep
|
||||
an existing toolbox unchanged.
|
||||
|
||||
## How it works
|
||||
|
||||
1. `projectClient.GetToolboxVersionAsync(name)` fetches the toolbox definition from the
|
||||
Foundry project API (resolving the default version if none is specified)
|
||||
2. `ToolboxVersion.ToAITools()` converts each tool definition to an `AITool` instance
|
||||
3. The tools are passed to `AsAIAgent(tools: ...)` which includes them in the Responses
|
||||
API request as server-side tool definitions
|
||||
|
||||
For a one-liner, use `projectClient.GetToolboxToolsAsync(name)` to fetch and convert in one call.
|
||||
|
||||
## Sample flows
|
||||
|
||||
| Flow | Description |
|
||||
|------|-------------|
|
||||
| `Main` (default) | Loads a single toolbox and runs an agent with its tools |
|
||||
| `CombineToolboxes` | Loads two toolboxes and merges their tools into one agent |
|
||||
|
||||
Uncomment the desired flow in the top-level statements to try each one.
|
||||
|
||||
## Running the sample
|
||||
|
||||
```bash
|
||||
dotnet run
|
||||
```
|
||||
@@ -73,6 +73,7 @@ Some samples require extra tool-specific environment variables. See each sample
|
||||
| [Memory search](./Agent_Step22_MemorySearch/) | Memory search tool |
|
||||
| [Local MCP](./Agent_Step23_LocalMCP/) | Local MCP client with HTTP transport |
|
||||
| [Code interpreter file download](./Agent_Step24_CodeInterpreterFileDownload/) | Download container files generated by code interpreter |
|
||||
| [Foundry toolbox via MCP](./Agent_Step25_FoundryToolboxMcp/) | Use a Foundry Toolbox from a non-hosted agent via its MCP endpoint |
|
||||
|
||||
## Running the samples
|
||||
|
||||
|
||||
Reference in New Issue
Block a user