Skip to content

Commit dd29f9a

Browse files
.NET: Hosted Agent Sample - Toolbox with various Auth (#5777) (#6018)
* .NET: Add Hosted-Toolbox-AuthPaths sample and auto-map /readiness with toolbox health gating (#5777) Add a new hosted agent sample demonstrating five MCP tool authentication paths (API key, agent MI, project MI, custom OAuth, literal token) via a Foundry Toolbox. Package changes (Microsoft.Agents.AI.Foundry.Hosting): - MapFoundryResponses now auto-maps GET /readiness via MapHealthChecks, idempotent across Tier 1/2 (AgentHost, already mapped) and Tier 3 (WebApplication, gap filled). - AddFoundryResponses registers AddHealthChecks() so the pipeline is available. - AddFoundryToolboxes registers FoundryToolboxHealthCheck on the /readiness aggregate, gating readiness on pre-registered toolbox startup outcome (per spec section 3.1). - FoundryToolboxService now exposes StartupStatus and FailedToolboxNames properties. New types: - FoundryToolboxStartupStatus (public enum): Pending, Healthy, Failed, NoEndpoint. - FoundryToolboxHealthCheck (internal IHealthCheck): adapts startup status to the AspNetCore HealthChecks pipeline with failed toolbox names in result data. Tests: - 3 new tests for /readiness auto-mapping (Tier 3 default, pre-mapped skip, idempotent). - 4 new tests for FoundryToolboxHealthCheck (Pending, NoEndpoint, Failed, Healthy). - 3 enhanced FoundryToolboxServiceTests with StartupStatus assertions. * .NET: Align FoundryToolboxService with tools-integration-spec (#5777 Part A) Bring Microsoft.Agents.AI.Foundry.Hosting's toolbox path into compliance with tools-integration-spec.md sections 2-4, 6.3, and 9. Empirically validated against tao-foundry-prj: the previous code (reading FOUNDRY_AGENT_TOOLSET_ENDPOINT, which the platform never injects) silently registered zero tools in production. Package changes (Microsoft.Agents.AI.Foundry.Hosting): - FoundryToolboxService.StartAsync now derives the toolbox proxy base URL from the platform-injected FOUNDRY_PROJECT_ENDPOINT and constructs the per-toolbox URL as {FOUNDRY_PROJECT_ENDPOINT}/toolboxes/{name}/mcp?api-version={ApiVersion} per spec sections 2-3. The legacy FOUNDRY_AGENT_TOOLSET_ENDPOINT env var is removed outright (preview package, no production consumers). - FoundryToolboxOptions.ApiVersion default flipped to 'v1' to match spec example. - FoundryToolboxBearerTokenHandler always sends the mandatory Foundry-Features: Toolboxes=V1Preview header per spec section 2, merging any additional flags supplied via the FOUNDRY_AGENT_TOOLSET_FEATURES env var. - FoundryToolboxBearerTokenHandler token scope changed from https://cognitiveservices.azure.com/.default to https://ai.azure.com/.default per spec section 4. - FoundryToolboxBearerTokenHandler propagates W3C trace context (traceparent, tracestate, baggage) from Activity.Current per spec section 6.3. Sample changes: - Hosted-Toolbox-AuthPaths and Hosted-Toolbox Program.cs, README.md, and .env.example corrected to describe the actual env-var contract (FOUNDRY_PROJECT_ENDPOINT auto-injected; AZURE_AI_PROJECT_ENDPOINT as the local-dev fallback). Removes the misleading 'auto-injected by Foundry runtime' claims for FOUNDRY_AGENT_TOOLSET_ENDPOINT. - Hosted-Toolbox-AuthPaths/agent.manifest.yaml declares the toolbox and model dependencies under resources[] per the AgentManifest schema so azd ai agent init users get them provisioned automatically. Tests: - 4 new FoundryToolboxServiceTests covering env-var derivation, EndpointOverride precedence, trailing-slash normalization, and the existing NoEndpoint behavior under the new env var name. - 4 new FoundryToolboxBearerTokenHandlerTests covering token scope, mandatory feature header always present, header merging with override, no duplicate mandatory flag, trace context propagation from Activity.Current, and no override of caller-set traceparent. - New FoundryProjectEndpointEnvFixture xUnit collection definition serializes env-var-mutating tests across FoundryToolboxServiceTests and FoundryToolboxHealthCheckTests, preventing parallel-execution races. - FoundryToolboxHealthCheckTests adjusted for the new env var name. * .NET: Drop ACA prereq from Hosted-Toolbox-AuthPaths README (#5777 Part B) Empirically verified that any Azure Cognitive Services MCP endpoint already in the Foundry project (e.g., a Language service MCP) accepts Entra tokens and can serve Paths 2 and 3 without deploying a separate Azure MCP Server to ACA. README updates: - Step 0 rewritten: 'Identify an Entra-authenticated MCP target in your project' instead of 'Deploy Azure MCP Server to Azure Container Apps' (the original azmcp-foundry-aca-mi setup is now optional, not required). - Auth-paths matrix updated to describe AAD-based connections targeting a Cognitive Services MCP URL (e.g., Language service) instead of an ACA URL. - Step 2 connections table updated: the Entra ID category is now a single 'AAD' authType. The original 'Agent Identity' vs 'Project Managed Identity' as selectable connection sub-types is NOT exposed via the ARM control plane today; the platform selects the calling principal contextually. Both connections in the walkthrough share the same shape and target. - Added an explicit RBAC note: the agent identity AND project MI must hold the required role (typically Cognitive Services User) on the target resource; without it the MCP server returns HTTP 401 even though the connection wiring is correct. - Toolbox tool entries renamed lang_entra_agent / lang_entra_project to match the new connection names. Empirical validation supporting these changes is captured in the session plan.md (Part B addendum). * .NET: Document correct connection shape for Hosted-Toolbox-AuthPaths Paths 2/3 (#5777) Updates the sample README with the verified connection shape and RBAC procedure for Microsoft Entra agent-identity and project-managed-identity MCP authentication: - Connection authType values: AgenticIdentityToken (agent identity) and ProjectManagedIdentity (project MI), both with category=RemoteTool. - Top-level audience property required; for Cognitive Services targets the value is https://cognitiveservices.azure.com. - Connections created via ARM REST (the Foundry portal wizard does not yet expose these authTypes). - RBAC grants target the project's shared agent identity blueprint principal (project.properties.agentIdentity.agentIdentityId) for Path 2 and the project's system-assigned MI (project.identity.principalId) for Path 3. - Troubleshooting table updated with the audience-mismatch symptom and the startup-cache behavior of FoundryToolboxService. * .NET: Drop Path 3 (project MI) and align with new agent model in Hosted-Toolbox-AuthPaths (#5777) Updates the sample to use only the new Foundry agent object model and removes the project managed identity path: - Auth-path matrix reduced to four paths: key, Entra agent identity, custom OAuth, inline authorization. Project managed identity is moved into a note describing when it applies (multiple agents sharing access) rather than as a documented sample path. - RBAC instructions reference the agent's own instance_identity.principal_id from the agent ARM resource (new agent object model) instead of the project's shared agent identity blueprint (legacy model). - Step 2 (connections) creates only the AgenticIdentityToken connection. - Step 3 (toolbox tools) lists four tool entries instead of five. - Sample prompts and troubleshooting table updated to match. * .NET: Restore Path 3 (project MI) to Hosted-Toolbox-AuthPaths matrix (#5777) The sample's purpose is to enumerate every authentication path a Foundry toolbox can drive, not to pick one. Path 3 belongs alongside the other four with explicit guidance for when each path is the right choice. - Path 3 (project managed identity, authType=ProjectManagedIdentity) restored to the matrix with a 'When to pick this' column. - Step 2 (connections) provisions both lang-mcp-agent-id and lang-mcp-project-mi via ARM REST. - Step 3 (toolbox) lists five tool entries (one per path). - RBAC instructions cover both the agent's instance identity (Path 2) and the project's system-assigned MI (Path 3). - Sample prompts include all five paths. - Troubleshooting table updated accordingly. * .NET: Fix duplicate line in Hosted-Toolbox-AuthPaths README (#5777) * .NET: Fix broken markdown link to ToolCallingApprovalHostedAgentFixture (#5777) * .NET: Fix relative path depth in markdown link (#5777) * .NET: Address Copilot review feedback for #5777 - FoundryToolboxHealthCheck description: rename FOUNDRY_AGENT_TOOLSET_ENDPOINT → FOUNDRY_PROJECT_ENDPOINT (stale reference; operator-facing in /readiness body). - FoundryToolboxStartupStatus.NoEndpoint XML doc: same rename. - ServiceCollectionExtensions XML docs: same rename + URL shape update. - Foundry.Hosting.IntegrationTests.TestContainer: remove explicit app.MapGet('/readiness') — now redundant + would conflict with the auto-mapped readiness route from MapFoundryResponses. - Hosted-Toolbox-AuthPaths agent.manifest.yaml: parameterize TOOLBOX_NAME via {{TOOLBOX_NAME}} template substitution and declare it under parameters with a default of 'auth-paths-toolbox' so the README's 'use any name' guidance actually works for hosted deployments. * .NET: Address Copilot review round 2 — fallback env + dedup + naming (#5777) - FoundryToolboxService.StartAsync: fall back to AZURE_AI_PROJECT_ENDPOINT when FOUNDRY_PROJECT_ENDPOINT is absent. Matches the local-dev convention used by the samples and resolves the doc/code mismatch flagged in review. - FoundryToolboxHealthCheck description updated for the fallback. - AddFoundryToolboxes: guard against duplicate health-check registration via an explicit name-uniqueness check on HealthCheckServiceOptions.Registrations. AddCheck<T>(name, ...) does not dedupe by name, so repeated AddFoundryToolboxes calls would have registered multiple instances. - FoundryToolboxOptions.EndpointOverride doc: clarify URL becomes {EndpointOverride}/toolboxes/{name}/mcp (was missing /toolboxes/ segment). - Hosted-Toolbox sample (Program.cs + README): switch FOUNDRY_TOOLBOX_NAME to TOOLBOX_NAME (the FOUNDRY_* prefix is reserved by the platform), default changed from 'my-toolset' to 'my-toolbox', terminology updated from 'Toolset' to 'Toolbox'. - FoundryToolboxServiceTests: 2 test renames to reflect what they actually assert (StartupStatus + FailedToolboxNames, not URL shape directly). - Tests adjusted to clear both env vars in NoEndpoint scenarios. * .NET: Fix stale NoEndpoint XML doc and misleading test comment (#5777) Update FoundryToolboxStartupStatus.NoEndpoint XML doc to mention both FOUNDRY_PROJECT_ENDPOINT and AZURE_AI_PROJECT_ENDPOINT (the service checks both since the fallback was added). Fix test comment that claimed URL derivation validation when the test only asserts on StartupStatus and FailedToolboxNames. * Remove OAuth consent path from AuthPaths sample, keep four working auth paths The interactive OAuth identity passthrough path needs a protocol gap closed in the hosting package (the proprietary oauth_consent_request item is not representable through the OpenAI/MEAI abstractions), so it is deferred to a separate spike branch. This strips the OAuth path from the AuthPaths sample, the companion REPL client, the agent manifest, and the docs, then renumbers the inline Authorization path so the sample teaches four contiguous paths: API key via connection, Entra agent identity, Entra project managed identity, and inline Authorization (anti-pattern). Package code is unchanged; the consent infrastructure already present in main stays as baseline. Both samples build with --warnaserror and all 246 hosting unit tests pass. * .NET: Drop project MI auth path and dedicated client from Hosted-Toolbox-AuthPaths (#5777) Live validation against tao-foundry-prj showed the ProjectManagedIdentity path failing with an unresolved token audience 401, so the sample now ships three working auth paths instead of four: connection key, agent managed identity, and inline Authorization. Changes: - Remove the project managed identity path from the AuthPaths sample matrix, prerequisites, connections, toolbox table, prompts, Program.cs instructions and agent.manifest.yaml. - Delete the near duplicate Hosted-Toolbox-AuthPaths-Client project and remove it from the solution. The README now drives the agent with the shared SimpleAgent REPL via AsAIAgent(agentEndpoint). - Correct the troubleshooting note: the Foundry toolbox tools/list is all or nothing, so one bad source returns -32007, fails startup, and returns 424 for every path. Add the allowed_tools caveat that names must match the upstream server. - Mark the toolbox startup status and health check experimental under AgentsAIExperiments (MAAI001) instead of AIOpenAIResponses, and update the package NoWarn set accordingly. * .NET: Address PR review nits for Hosted-Toolbox-AuthPaths (#5777) - Remove duplicated NU1903 comment in Foundry.Hosting csproj. - Fix stale 'four-tool' cross-links in Hosted-Toolbox and Hosted-McpTools READMEs to describe the three-path toolbox driven by the shared SimpleAgent REPL. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Address toolbox startup-status review feedback (#5777) - Rename FoundryToolboxStartupStatus.Failed to Unhealthy so it is the proper opposite of Healthy, and clarify the doc comment covers the partial-failure case. - Raise the missing-endpoint toolbox log from Information to Warning, since enabling toolboxes is an explicit opt-in and a silently disabled toolbox warrants a higher-severity signal. - Update unit tests and the AuthPaths README troubleshooting row accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Reword toolbox-wiring comment to avoid hosting-layer internals (#5777) Address PR review feedback: explain how a Foundry Toolbox is attached using the public API (AddFoundryToolboxes vs the CreateHostedMcpToolbox marker) and observable behavior, instead of naming the internal AgentFrameworkResponseHandler type and FoundryToolboxService.Tools property. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a5f4e00 commit dd29f9a

25 files changed

Lines changed: 1390 additions & 54 deletions

dotnet/agent-framework-dotnet.slnx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,9 @@
344344
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox/">
345345
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox/HostedToolbox.csproj" />
346346
</Folder>
347+
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox-AuthPaths/">
348+
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox-AuthPaths/Hosted-Toolbox-AuthPaths.csproj" />
349+
</Folder>
347350
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/">
348351
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/HostedToolboxMcpSkills.csproj" />
349352
</Folder>

dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,9 @@ For end-to-end hosted agent deployment guidance, see the [official deployment gu
109109
## NuGet package users
110110

111111
Use the standard `Dockerfile` instead of `Dockerfile.contributor`. See the commented section in `HostedMcpTools.csproj` for the `PackageReference` alternative.
112+
113+
## Related samples
114+
115+
- [`Hosted-Toolbox/`](../Hosted-Toolbox/) — connects to a single Foundry Toolbox via the AF Foundry hosting bridge (`AddFoundryToolboxes` + `FoundryAITool.CreateHostedMcpToolbox`).
116+
- [`Hosted-Toolbox-AuthPaths/`](../Hosted-Toolbox-AuthPaths/) — same hosting bones as `Hosted-Toolbox/`, but the toolbox bundles three MCP tools each authenticated differently (key, Entra agent identity, inline `Authorization`), driven by the shared `Using-Samples/SimpleAgent/` REPL.
117+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Azure AI Foundry project endpoint (auto-injected in hosted containers).
2+
AZURE_AI_PROJECT_ENDPOINT=https://<your-foundry-account>.services.ai.azure.com/api/projects/<your-project>
3+
4+
# Model deployment name. Must exist in the Foundry project above.
5+
AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
6+
7+
# Name of the Foundry Toolbox you provisioned in the portal (see README.md).
8+
TOOLBOX_NAME=auth-paths-toolbox
9+
10+
# Agent name advertised over the wire. Must be unique if running side-by-side with
11+
# other Hosted-* samples (e.g. Hosted-Toolbox), otherwise the REPL client cannot
12+
# disambiguate which agent to chat with.
13+
AGENT_NAME=hosted-toolbox-auth-paths-agent
14+
15+
# Application Insights connection string (auto-injected in hosted containers; optional locally).
16+
# APPLICATIONINSIGHTS_CONNECTION_STRING=InstrumentationKey=...
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Use the official .NET 10.0 ASP.NET runtime as a parent image
2+
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
3+
WORKDIR /app
4+
5+
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
6+
WORKDIR /src
7+
COPY . .
8+
RUN dotnet restore
9+
RUN dotnet publish -c Release -o /app/publish
10+
11+
# Final stage
12+
FROM base AS final
13+
WORKDIR /app
14+
COPY --from=build /app/publish .
15+
EXPOSE 8088
16+
ENV ASPNETCORE_URLS=http://+:8088
17+
ENTRYPOINT ["dotnet", "HostedToolboxAuthPaths.dll"]
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Dockerfile for contributors building from the agent-framework repository source.
2+
#
3+
# This project uses ProjectReference to the local source, which means a standard
4+
# multi-stage Docker build cannot resolve dependencies outside this folder.
5+
# Pre-publish the app targeting the container runtime and copy the output:
6+
#
7+
# dotnet publish -c Debug -f net10.0 -r linux-musl-x64 --self-contained false -o out
8+
# docker build -f Dockerfile.contributor -t hosted-toolbox-auth-paths .
9+
# docker run --rm -p 8088:8088 \
10+
# -e AGENT_NAME=hosted-toolbox-auth-paths-agent \
11+
# -e AZURE_BEARER_TOKEN=$AZURE_BEARER_TOKEN \
12+
# --env-file .env hosted-toolbox-auth-paths
13+
#
14+
# For end-users consuming the NuGet package (not ProjectReference), use the standard
15+
# Dockerfile which performs a full dotnet restore + publish inside the container.
16+
FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final
17+
WORKDIR /app
18+
COPY out/ .
19+
EXPOSE 8088
20+
ENV ASPNETCORE_URLS=http://+:8088
21+
ENTRYPOINT ["dotnet", "HostedToolboxAuthPaths.dll"]
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net10.0</TargetFrameworks>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
8+
<RootNamespace>HostedToolboxAuthPaths</RootNamespace>
9+
<AssemblyName>HostedToolboxAuthPaths</AssemblyName>
10+
<NoWarn>$(NoWarn);OPENAI001</NoWarn>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="Azure.AI.Projects" />
15+
<PackageReference Include="Azure.Identity" />
16+
<PackageReference Include="DotNetEnv" />
17+
</ItemGroup>
18+
19+
<!-- For contributors: uses ProjectReference to build against local source -->
20+
<ItemGroup>
21+
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
22+
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Foundry.Hosting\Microsoft.Agents.AI.Foundry.Hosting.csproj" />
23+
<ProjectReference Include="..\Hosted_Shared_Contributor_Setup\Hosted_Shared_Contributor_Setup.csproj" />
24+
</ItemGroup>
25+
26+
<!-- For end-users: uncomment the PackageReference below and remove the ProjectReference above
27+
<ItemGroup>
28+
<PackageReference Include="Microsoft.Agents.AI.Foundry" Version="1.0.0" />
29+
<PackageReference Include="Microsoft.Agents.AI.Foundry.Hosting" Version="1.0.0" />
30+
</ItemGroup>
31+
-->
32+
33+
</Project>
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
// Foundry Toolbox Auth Paths Agent — A hosted agent backed by a single Foundry Toolbox
4+
// that bundles MCP tools using THREE different authentication paths.
5+
//
6+
// This sample demonstrates the same hosting bones as Hosted-Toolbox/, but the toolbox
7+
// (provisioned by the user out-of-band) contains three MCP tool entries each authenticated
8+
// differently. The agent code itself is agnostic to authentication — the educational
9+
// surface lives in the toolbox configuration in the Foundry portal and in this sample's
10+
// README.md.
11+
//
12+
// Required environment variables:
13+
// AZURE_AI_PROJECT_ENDPOINT (local-dev) OR FOUNDRY_PROJECT_ENDPOINT (hosted runtime)
14+
// - Azure AI Foundry project endpoint. The Foundry hosted
15+
// runtime auto-injects FOUNDRY_PROJECT_ENDPOINT; locally
16+
// set AZURE_AI_PROJECT_ENDPOINT (the AF-repo convention).
17+
// TOOLBOX_NAME - Name of the Foundry Toolbox to load
18+
// (default: auth-paths-toolbox)
19+
//
20+
// Optional:
21+
// AZURE_AI_MODEL_DEPLOYMENT_NAME - Model deployment name (default: gpt-4o)
22+
// AGENT_NAME - Defaults to "hosted-toolbox-auth-paths-agent".
23+
//
24+
// The Foundry.Hosting package builds the toolbox proxy URL from FOUNDRY_PROJECT_ENDPOINT
25+
// per tools-integration-spec.md §2–§3, so the sample does not need to plumb any
26+
// toolbox-specific URL env var.
27+
//
28+
// NOTE: All FOUNDRY_* and AGENT_* env-var prefixes (other than the platform-injected ones
29+
// listed above) are reserved by the Foundry container platform and rejected by the
30+
// agent-create API. Use TOOLBOX_NAME, not FOUNDRY_TOOLBOX_NAME, for sample-owned config.
31+
32+
#pragma warning disable OPENAI001 // FoundryAITool.CreateHostedMcpToolbox is experimental
33+
34+
using Azure.AI.Projects;
35+
using Azure.Core;
36+
using Azure.Identity;
37+
using DotNetEnv;
38+
using Hosted_Shared_Contributor_Setup;
39+
using Microsoft.Agents.AI;
40+
using Microsoft.Agents.AI.Foundry.Hosting;
41+
42+
// Load .env file if present (for local development)
43+
Env.TraversePath().Load();
44+
45+
// Project endpoint resolution order:
46+
// 1. FOUNDRY_PROJECT_ENDPOINT — auto-injected by the Foundry hosted runtime.
47+
// 2. AZURE_AI_PROJECT_ENDPOINT — the convention developers set locally for `dotnet run`.
48+
// When deployed, only (1) is available; the AF-repo sample convention to set (2) at
49+
// deploy time fails silently because the platform reserves all FOUNDRY_* env-var names
50+
// and rejects them at agent-create time. Read both, prefer the platform-injected one.
51+
string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
52+
?? Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
53+
?? throw new InvalidOperationException(
54+
"Neither FOUNDRY_PROJECT_ENDPOINT (platform-injected in hosted runtime) " +
55+
"nor AZURE_AI_PROJECT_ENDPOINT (local-dev convention) is set.");
56+
string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";
57+
string toolboxName = Environment.GetEnvironmentVariable("TOOLBOX_NAME") ?? "auth-paths-toolbox";
58+
string agentName = Environment.GetEnvironmentVariable("AGENT_NAME") ?? "hosted-toolbox-auth-paths-agent";
59+
60+
TokenCredential credential = new ChainedTokenCredential(
61+
new DevTemporaryTokenCredential(),
62+
new DefaultAzureCredential());
63+
64+
// Notes on toolbox wiring — there are two ways to attach a Foundry Toolbox to an agent:
65+
// - Server-side "baked-in" (what this sample uses): calling AddFoundryToolboxes(name)
66+
// below registers the toolbox with the Foundry.Hosting layer, which resolves that
67+
// toolbox's MCP tools once at startup and automatically makes them available to the
68+
// agent on every request. The agent code does nothing per request.
69+
// - Per-request / caller-driven (NOT used here): a client can attach a toolbox for a
70+
// single call by placing a FoundryAITool.CreateHostedMcpToolbox(name) marker in the
71+
// request body's tool list.
72+
// Because this sample bakes the toolbox in on the server, it uses AddFoundryToolboxes and
73+
// does NOT put the CreateHostedMcpToolbox marker in the agent's `tools:` array.
74+
AIAgent agent = new AIProjectClient(new Uri(endpoint), credential)
75+
.AsAIAgent(
76+
model: deploymentName,
77+
instructions: """
78+
You are a helpful assistant with access to several tools, each provided by a different
79+
upstream service authenticated through a distinct mechanism (API key, agent managed
80+
identity, and a literal token
81+
shipped with the tool definition). Pick the tool that best fits the user's question
82+
and explain which upstream service answered when you respond.
83+
""",
84+
name: agentName,
85+
description: "Hosted agent demonstrating three MCP-tool authentication paths via a Foundry Toolbox.");
86+
87+
// Tier 3 spine (WebApplication.CreateBuilder + AddFoundryResponses + MapFoundryResponses):
88+
// the Foundry.Hosting package auto-maps the spec-required GET /readiness probe inside
89+
// MapFoundryResponses (idempotent — skipped when AgentHost or the developer already
90+
// mapped it), so the sample stays free of platform plumbing.
91+
var builder = WebApplication.CreateBuilder(args);
92+
93+
builder.Services.AddFoundryResponses(agent);
94+
// Pre-register the toolbox name so FoundryToolboxService resolves the foundry-toolbox://
95+
// marker at request time. With FOUNDRY_PROJECT_ENDPOINT injected by the platform, startup
96+
// MCP tools/list against the toolbox proxy is typically <100ms in-region.
97+
builder.Services.AddFoundryToolboxes(toolboxName);
98+
99+
var app = builder.Build();
100+
app.MapFoundryResponses();
101+
102+
// Contributor-only: in Development, also map the per-agent OpenAI route shape that live Foundry
103+
// uses so a local REPL client can target this server via AIProjectClient.AsAIAgent(Uri agentEndpoint).
104+
// Do not use this in production. Hosted Foundry agents only support the agent-endpoint path.
105+
app.MapDevTemporaryLocalAgentEndpoint();
106+
107+
app.Run();
108+
109+
// ── DevTemporaryTokenCredential ───────────────────────────────────────────────
110+
111+
/// <summary>
112+
/// A <see cref="TokenCredential"/> for local Docker debugging only.
113+
/// Reads a pre-fetched bearer token from the <c>AZURE_BEARER_TOKEN</c> environment variable
114+
/// once at startup. This should NOT be used in production.
115+
///
116+
/// Generate a token on your host and pass it to the container:
117+
/// export AZURE_BEARER_TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)
118+
/// docker run -e AZURE_BEARER_TOKEN=$AZURE_BEARER_TOKEN ...
119+
/// </summary>
120+
internal sealed class DevTemporaryTokenCredential : TokenCredential
121+
{
122+
private const string EnvironmentVariable = "AZURE_BEARER_TOKEN";
123+
private readonly string? _token;
124+
125+
public DevTemporaryTokenCredential()
126+
{
127+
this._token = Environment.GetEnvironmentVariable(EnvironmentVariable);
128+
}
129+
130+
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
131+
=> this.GetAccessToken();
132+
133+
public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
134+
=> new(this.GetAccessToken());
135+
136+
private AccessToken GetAccessToken()
137+
{
138+
if (string.IsNullOrEmpty(this._token) || this._token == "DefaultAzureCredential")
139+
{
140+
throw new CredentialUnavailableException($"{EnvironmentVariable} environment variable is not set.");
141+
}
142+
143+
return new AccessToken(this._token, DateTimeOffset.MaxValue);
144+
}
145+
}

0 commit comments

Comments
 (0)