Commit Graph

2 Commits

  • .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>
  • .NET: Add dedicated Foundry.Hosting UnitTest project (#5592)
    * Foundry.Hosting.UnitTests: extract project from Foundry.UnitTests
    
    Move all Hosting/* tests, three toolbox TestData JSONs, and the FakeAuthenticationTokenProvider/HttpHandlerAssert/TestDataUtil helpers (trimmed to toolbox getters) into a new Microsoft.Agents.AI.Foundry.Hosting.UnitTests project. Add it to the slnx and grant the new assembly InternalsVisibleTo from Microsoft.Agents.AI.Foundry and Microsoft.Agents.AI.Foundry.Hosting.
    
    * Foundry.Hosting.UnitTests: align namespaces to assembly name
    
    Rename namespaces from Microsoft.Agents.AI.Foundry.UnitTests(.Hosting) to Microsoft.Agents.AI.Foundry.Hosting.UnitTests across all moved tests, the duplicated helpers, and the trimmed TestDataUtil. Also fixes the prior namespace inconsistency in FoundryToolboxTests.
    
    * Foundry.Hosting.UnitTests: split WorkflowIntegrationTests by SUT
    
    Replace the WorkflowIntegrationTests file (an IT-named file inside a UT project) with two SUT-focused files plus a shared test-doubles file:
    
    - AgentFrameworkResponseHandlerWorkflowTests.cs - the 5 handler-driven tests that exercise AgentFrameworkResponseHandler with a real workflow agent.
    - OutputConverterWorkflowTests.cs - the 5 OutputConverter tests driven by hand-crafted update sequences mirroring real workflow patterns.
    - WorkflowTestAgents.cs - StreamingTextAgent and ThrowingStreamingAgent extracted as internal types used by both files.
    
    * Foundry.UnitTests: trim Hosting-related conditionals and dead testdata
    
    Now that Hosting tests live in their own project:
    - drop the Compile Remove guard for the Hosting subfolder,
    - drop the .NETCoreApp-only PackageReferences (Azure.AI.AgentServer.Responses, Microsoft.AspNetCore.TestHost, OpenTelemetry, OpenTelemetry.Exporter.InMemory),
    - drop the conditional ProjectReference to Microsoft.Agents.AI.Foundry.Hosting,
    - delete the three Toolbox JSON files and the matching Toolbox getters in TestDataUtil.
    
    * Foundry.Hosting.UnitTests: drop redundant 'using Microsoft.Agents.AI.Foundry.Hosting'
    
    The new project namespace is Microsoft.Agents.AI.Foundry.Hosting.UnitTests, which already brings the parent Microsoft.Agents.AI.Foundry.Hosting namespace into scope. The explicit using statement is therefore redundant (IDE0005). Caught by 'dotnet format --verify-no-changes' running on Linux against the .NET 10 SDK.
    
    * Foundry.Hosting: drop InternalsVisibleTo to Foundry.UnitTests
    
    The non-hosting Foundry.UnitTests project no longer holds any Hosting tests after the split, so it doesn't need access to internal types in Microsoft.Agents.AI.Foundry.Hosting. Only Microsoft.Agents.AI.Foundry.Hosting.UnitTests needs it.
    
    * Foundry.Hosting: rename DelegatingResponsesClient to UserAgentResponsesClient
    
    Address westey-m's review feedback on PR #5453: `Delegating*` is conventionally reserved for inheritable base classes (mirroring `DelegatingHandler`) where consumers override one or two members. This polyfill is sealed and only injects the User-Agent supplement, so the new name reflects its actual purpose.
    
    Renamed via `git mv` to preserve history:
    * `src/Microsoft.Agents.AI.Foundry.Hosting/DelegatingResponsesClient.cs` to `UserAgentResponsesClient.cs`
    * `tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/DelegatingResponsesClientTests.cs` to `UserAgentResponsesClientTests.cs`
    
    Class, constructor, and all references updated across:
    * `src/.../UserAgentResponsesClient.cs` (class + constructor + internal log message)
    * `src/.../ServiceCollectionExtensions.cs` (cref + type check + instantiation)
    * `src/.../HostedAgentUserAgentPolicy.cs` (cref)
    * `tests/Foundry.UnitTests/RequestOptionsExtensionsTests.cs` (comment)
    * `tests/Foundry.Hosting.UnitTests/UserAgentResponsesClientTests.cs` (class + cref + instantiations)