diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/.azdignore b/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/.azdignore index 7eac03bae35..a53a7405dbc 100644 --- a/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/.azdignore +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/.azdignore @@ -1,6 +1,7 @@ -agent.manifest.yaml -agent.yaml -.env.example .env -toolbox.yaml +.azure +.venv +__pycache__ +.pytest_cache +tests ./scripts \ No newline at end of file diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/.dockerignore b/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/.dockerignore index 0d6619bae00..e5ca4a902f0 100644 --- a/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/.dockerignore +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/.dockerignore @@ -4,4 +4,8 @@ __pycache__ *.pyo *.pyd .Python -.env \ No newline at end of file +.env +.azure +.git +.pytest_cache +tests \ No newline at end of file diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/.env.example b/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/.env.example index 1e51656a707..7aefd6e58ae 100644 --- a/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/.env.example +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/.env.example @@ -1,3 +1,5 @@ -FOUNDRY_PROJECT_ENDPOINT="..." -FOUNDRY_MODEL="..." -TOOLBOX_ENDPOINT="..." \ No newline at end of file +# Copy to .env for standalone Python or VS Code runs after deploying dependencies. +FOUNDRY_PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/" +AZURE_AI_MODEL_DEPLOYMENT_NAME="" +# From: azd env get-value TOOLBOX_AGENT_TOOLS_MCP_ENDPOINT +TOOLBOX_ENDPOINT="https://.services.ai.azure.com/api/projects//toolboxes/agent-tools/versions//mcp?api-version=v1" \ No newline at end of file diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/README.md b/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/README.md index ffd619ea9a5..b5af9b55ac5 100644 --- a/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/README.md +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/README.md @@ -1,99 +1,48 @@ # Agent with Foundry Toolbox (Responses Protocol) -An [Agent Framework](https://github.com/microsoft/agent-framework) agent that uses **Foundry Toolbox** for tool discovery, hosted on Microsoft Foundry using the **Responses protocol**. Foundry Toolbox is a managed tool registry in Microsoft Foundry that lets you define tools centrally and share them across agents. - -## Creating a Foundry Toolbox - -You can create a Foundry Toolbox by code. Refer to this sample for an example: [Foundry Toolbox CRUD Sample](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/samples/toolboxes/sample_toolboxes_crud.py). - -You can also create a Foundry Toolbox in the Foundry portal. Read more about it [in the Foundry toolbox documentation](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/tools/toolbox). - -This sample consumes a toolbox over its MCP endpoint. It bundles a [`toolbox.yaml`](toolbox.yaml) that defines 6 tools behind one endpoint: - -- **Web search**, which grounds responses in real-time public web results. -- **Code interpreter**, which executes Python code in a secure sandbox and returns the output. -- **Azure Specs MCP**, which demonstrates connecting to an MCP server that doesn't require authentication. -- **GitHub MCP**, which demonstrates connecting to the GitHub MCP server using either a Personal Access Token (PAT) or OAuth2 (switch by changing the `project_connection_id` in `toolbox.yaml`). -- **Azure Language MCP with agent identity**, which demonstrates connecting to the Azure Language MCP server using agent identity for authentication. -- **Microsoft Foundry MCP with Entra pass-through**, which demonstrates connecting to the Microsoft Foundry MCP server using Entra pass-through for authentication. - -### Authentication Methods - -You can connect to MCP servers in Foundry Toolbox that use different authentication methods. This sample demonstrates the following authentication methods: - -- [**No authentication**](https://github.com/microsoft-foundry/foundry-samples/blob/main/samples/python/hosted-agents/SUPPORTED_TOOLBOX_SCENARIOS/tools/mcp-unauthenticated.md): The tool does not require any authentication. The agent can invoke the tool without providing any credentials. Sample MCP server: `https://gitmcp.io/Azure/azure-rest-api-specs` -- [**Key-based authentication**](https://github.com/microsoft-foundry/foundry-samples/blob/main/samples/python/hosted-agents/SUPPORTED_TOOLBOX_SCENARIOS/tools/mcp-key-auth.md): The tool requires a key to authenticate. Sample MCP server: `https://api.githubcopilot.com/mcp` (GitHub MCP server) with a Personal Access Token (PAT) for authentication. -- [**OAuth2 authentication (managed)**](https://github.com/microsoft-foundry/foundry-samples/blob/main/samples/python/hosted-agents/SUPPORTED_TOOLBOX_SCENARIOS/tools/mcp-oauth-managed.md): The tool requires OAuth2 to authenticate. Sample MCP server: `https://api.githubcopilot.com/mcp` (GitHub MCP server) with OAuth2 for authentication. -- [**Agent identity authentication**](https://github.com/microsoft-foundry/foundry-samples/blob/main/samples/python/hosted-agents/SUPPORTED_TOOLBOX_SCENARIOS/tools/mcp-microsoft-entra.md): The tool requires an agent identity token to authenticate. Sample MCP server: `https://{foundry-resource-name}.cognitiveservices.azure.com/language/mcp?api-version=2025-11-15-preview` ([Azure Language MCP server](https://learn.microsoft.com/en-us/azure/ai-services/language-service/concepts/foundry-tools-agents#azure-language-mcp-server-preview)) with agent identity for authentication. -- [**Entra Pass-through authentication**](https://github.com/microsoft-foundry/foundry-samples/blob/main/samples/python/hosted-agents/SUPPORTED_TOOLBOX_SCENARIOS/tools/mcp-user-entra-token.md): The tool requires an Entra pass-through token to authenticate; Foundry forwards the calling user's Entra token to the MCP server. Sample MCP server: the [Microsoft Foundry MCP server](https://learn.microsoft.com/en-us/azure/foundry/mcp/get-started?view=foundry&tabs=user), which exposes Foundry model-catalog, evaluation, agent, and session tools and requires only that the caller have access to the Foundry project (no extra license). - -There are also Non-MCP tools in the toolbox that support different authentication methods. Learn more at the [Foundry sample repository](https://github.com/microsoft-foundry/foundry-samples/blob/main/samples/python/hosted-agents/SUPPORTED_TOOLBOX_SCENARIOS/README.md). - -### Finding the Entra audience for an MCP server - -An Entra pass-through connection requires an **audience** — the Entra resource that the MCP server validates tokens against. For the Microsoft Foundry MCP server (`https://mcp.ai.azure.com`), read it from the server's OAuth protected-resource metadata: - -```bash -curl https://mcp.ai.azure.com/.well-known/oauth-protected-resource -``` - -```jsonc -{ - "resource": "https://mcp.ai.azure.com", - "authorization_servers": ["https://login.microsoftonline.com/common/v2.0"], - "scopes_supported": ["https://mcp.ai.azure.com/Foundry.Mcp.Tools"] -} -``` - -Use the `resource` value (`https://mcp.ai.azure.com`) as the audience. - -> For connector-backed MCP servers (for example Microsoft 365 / WorkIQ servers such as Outlook Mail), the audience is instead published in the Foundry Tools Catalog. Look it up with the helper scripts in [`scripts/`](scripts/): run `./scripts/list-foundry-connectors.ps1 -ConnectorName ` (or `./scripts/list-foundry-connectors.sh -n `) and read `AzureActiveDirectoryResourceId` (equivalently `resourceUri`) under `properties.x-ms-connection-parameters`. Run the script with no connector name to list every connector with its name, title, and auth type. - -### Creating Connections - -Before creating the toolbox, create project connections for any tools that require authentication. The connection defines the authentication details and credentials for the tool, and the toolbox references the connection to authenticate tool invocations at runtime. The following connections are needed for this sample (used in `toolbox.yaml`): - -For `ghmcppat`, run the following command to create a PAT-based connection to the GitHub MCP server: - -```powershell -azd ai connection create ghmcppat --kind remote-tool --target https://api.githubcopilot.com/mcp --auth-type custom-keys --custom-key "Authorization=Bearer " -p https://.services.ai.azure.com/api/projects/ -``` - -For `ghmcpoauth`, create an OAuth2-based connection to the GitHub MCP server: - -```powershell -azd ai connection create ghmcpoauth --kind remote-tool --target https://api.githubcopilot.com/mcp --auth-type oauth2 --connector-name foundrygithubmcp -p https://.services.ai.azure.com/api/projects/ -``` - -> This sample uses `ghmcppat` by default, but you can switch to `ghmcpoauth` in the `toolbox.yaml` file. - -For `langmcpconn`, create an agent-identity-based connection to the Azure Language MCP server: - -```powershell -azd ai connection create langmcpconn --kind remote-tool --target https://.cognitiveservices.azure.com/language/mcp?api-version=2025-11-15-preview --auth-type project-managed-identity --audience https://cognitiveservices.azure.com/ -p https://.services.ai.azure.com/api/projects/ -``` - -For `foundrymcpconn`, create an Entra pass-through connection to the Microsoft Foundry MCP server: - -```powershell -azd ai connection create foundrymcpconn --kind remote-tool --target https://mcp.ai.azure.com --auth-type user-entra-token --audience https://mcp.ai.azure.com -p https://.services.ai.azure.com/api/projects/ -``` - -### Creating the toolbox - -You create the toolbox once from `toolbox.yaml`, then copy the versioned MCP endpoint it prints into the `TOOLBOX_ENDPOINT` environment variable. The agent connects to that endpoint at runtime. - -```powershell -azd ai toolbox create agent-tools --from-file ./toolbox.yaml --project-endpoint https://.services.ai.azure.com/api/projects/ -``` +An [Agent Framework](https://github.com/microsoft/agent-framework) agent that uses **Foundry Toolbox** +for tool discovery, hosted on Microsoft Foundry using the **Responses protocol**. Foundry Toolbox +is a managed tool registry that lets you define tools centrally and share them across agents. + +## Foundry Toolbox configuration + +The unified [`azure.yaml`](azure.yaml) replaces the legacy agent manifests and standalone toolbox +definition. It declares the Foundry project, model deployment, authenticated connections, toolbox, +and hosted agent together. The `uses` dependencies deploy connections before the toolbox and the +toolbox before the agent. + +The toolbox exposes six tool sources over one MCP endpoint: + +| Source | Authentication | Connection | +|--------|----------------|------------| +| Web search | Built-in tool | None | +| Code interpreter | Built-in tool | None | +| Azure Specs MCP | No authentication | None | +| GitHub MCP | PAT in an Authorization header | `ghmcppat` | +| Azure Language MCP | Project managed identity | `langmcpconn` | +| Microsoft Foundry MCP | Calling user's Entra token | `foundrymcpconn` | + +The toolbox's `connection` references resolve to project connection IDs at deployment time. +The PAT is read from the azd environment and sent as an `Authorization: Bearer` header; no +credentials belong in the YAML. To use OAuth instead, change the GitHub connection's authentication +configuration using the [managed OAuth guide](https://github.com/microsoft-foundry/foundry-samples/blob/main/samples/python/hosted-agents/SUPPORTED_TOOLBOX_SCENARIOS/tools/mcp-oauth-managed.md). +Changing only a toolbox reference does not configure OAuth. + +For other tool types and authentication modes, see the +[supported toolbox scenarios](https://github.com/microsoft-foundry/foundry-samples/blob/main/samples/python/hosted-agents/SUPPORTED_TOOLBOX_SCENARIOS/README.md) +and [Foundry toolbox documentation](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/tools/toolbox). ## How it works -### Model Integration +### Model integration -The agent uses `FoundryChatClient` from the Agent Framework to create an OpenAI-compatible Responses client. It connects to the toolbox's MCP endpoint via `MCPStreamableHTTPTool`, which discovers and invokes the toolbox's tools over MCP at runtime. The agent resolves the endpoint from the `TOOLBOX_ENDPOINT` environment variable. If that variable isn't set, it builds the unversioned (default-version) endpoint from `FOUNDRY_PROJECT_ENDPOINT` and `TOOLBOX_NAME`. +The agent uses `FoundryChatClient` to create an OpenAI-compatible Responses client. `FoundryToolbox` +authenticates toolbox MCP requests and forwards the current platform headers while discovering +and invoking tools. It resolves `TOOLBOX_ENDPOINT`, or falls back to the default-version endpoint +built from `FOUNDRY_PROJECT_ENDPOINT` and `TOOLBOX_NAME`. -See [main.py](main.py) for the full implementation. +Deployment maps the versioned `TOOLBOX_AGENT_TOOLS_MCP_ENDPOINT` output to the agent's +`TOOLBOX_ENDPOINT`. See [main.py](main.py) for the implementation. ### Request-owned resources @@ -118,7 +67,8 @@ require the appropriate pass-through connection and the current call ID. A fresh Python object does not by itself grant external permissions. GitHub PAT/OAuth connections, project roles and consent are separate setup, not offline coverage. Native code-interpreter file citations are a separate tracked behavior -(microsoft/agent-framework#7916), not implemented by this lifecycle change. +([microsoft/agent-framework#7916](https://github.com/microsoft/agent-framework/issues/7916)), +not implemented by this configuration migration. ## Running the agent @@ -126,175 +76,227 @@ Native code-interpreter file citations are a separate tracked behavior #### Prerequisites -1. **Azure Developer CLI (`azd`)** — [Install azd](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd) (1.25 or later) -2. Install the unified Foundry CLI extension bundle (provides `azd ai agent`, `connection`, `inspector`, `project`, `routine`, `skill`, and `toolbox`): +Prepare external credentials and permissions **before initializing**; init can prompt for the PAT +and Language endpoint. + +1. **GitHub PAT**: [Create a fine-grained personal access token](https://github.com/settings/personal-access-tokens/new). + Limit repository access and permissions to the GitHub MCP tools you intend to use. Supply the token + without a `Bearer` prefix; the connection adds it. Keep the PAT out of source control. +2. **Azure Language resource**: obtain its + [Language MCP endpoint](https://learn.microsoft.com/en-us/azure/ai-services/language-service/concepts/foundry-tools-agents#azure-language-mcp-server-preview). + The Foundry project's managed identity needs **Cognitive Services Language Owner** on this resource. + Arrange permission to make that role assignment. For an existing project, assign the role after init + and before `azd up`. For a new project, the identity exists only after `azd provision`; see + [Provision and deploy dependencies and agent](#provision-and-deploy-dependencies-and-agent). +3. **Foundry access**: the account you use with azd needs access to the selected Foundry project. + The Foundry MCP connection forwards the calling user's Entra token and does not grant additional access. +4. **Azure Developer CLI (`azd`)**: [Install azd](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd) + 1.32.0 or later. The minimum extension versions are declared in `azure.yaml`. +5. Install the unified Foundry CLI extension bundle (provides agent, connection, inspector, project, + routine, skill, and toolbox commands): ```bash # If you previously installed individual extensions, uninstall them first: - # azd ext uninstall azure.ai.agents - # azd ext uninstall azure.ai.toolboxes + # azd ext uninstall azure.ai.agents + # azd ext uninstall azure.ai.toolboxes azd ext install microsoft.foundry ``` -3. Authenticate: +6. Authenticate deployment commands: ```bash azd auth login ``` #### Initialize the agent project -No cloning required. Create a new folder and initialize from the manifest: +No cloning required. Create an empty folder and adopt this Agent Framework sample's unified +configuration. Here `--src .` selects the current folder as the **project destination**, not a separate +source directory: ```bash -mkdir my-toolbox-agent && cd my-toolbox-agent +mkdir my-toolbox-agent +cd my-toolbox-agent -azd ai agent init -m https://github.com/microsoft-foundry/foundry-samples/blob/main/samples/python/hosted-agents/agent-framework/responses/04-foundry-toolbox/agent.manifest.yaml +azd ai agent init --template https://github.com/microsoft/agent-framework/blob/main/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/azure.yaml --src . ``` -Follow the prompts to configure your Foundry project and model deployment. If you don't have an existing Foundry project, `azd ai agent init` will guide you through creating one. Initializing also sets the selected project as the active project for the `azd ai` commands that follow. +Follow the prompts to create or select the Foundry project and provide external inputs if requested. +For an existing Foundry project, append `--model-deployment ` to select its +deployment explicitly. This works around the current +[Azure/azure-dev#10519 model-picker issue](https://github.com/Azure/azure-dev/issues/10519). +For a new deployment, the template declares `gpt-4.1`. + +Initialization places `azure.yaml`, `main.py`, `requirements.txt`, and the `.azure` environment in +`my-toolbox-agent`. The agent's `project: .` keeps its source in that same root. Run all commands below +from this root; there is no generated `src/` directory. Do not initialize again after success. +For a repository checkout, work from this sample directory and run `azd ai agent init .`. Accept reuse +of the existing configuration rather than adopting a second manifest over the existing `azure.yaml`. -#### Create the toolbox with `azd ai` +The project managed identity needs **Cognitive Services Language Owner** on the Language resource. See the +[identity authorization guide](https://github.com/microsoft-foundry/foundry-samples/blob/main/samples/python/hosted-agents/SUPPORTED_TOOLBOX_SCENARIOS/tools/mcp-microsoft-entra.md#step-2--authorize-the-identity). +When you grant it depends on your project: -> [!TIP] -> If you use GitHub Copilot for Azure to scaffold a hosted agent that consumes this toolbox, the following skill references describe the same endpoint contract (env var, headers, MCP protocol, citation patterns, and troubleshooting) that the agent must implement: -> -> - [Toolbox overview](https://github.com/microsoft/GitHub-Copilot-for-Azure/blob/main/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/toolbox/toolbox.md) — toolbox concept, API shape and schema, versions, endpoints, and MCP protocol. -> - [Use toolbox in a hosted agent](https://github.com/microsoft/GitHub-Copilot-for-Azure/blob/main/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/use-toolbox-in-hosted-agent.md) — endpoint resolution, env-var contract, payload shape, code integration patterns, and tracing. +- **Existing Foundry project:** grant the role after init and before `azd up`. +- **New Foundry project:** init only writes configuration, and provisioning creates the project and its + identity. Run `azd provision`, grant the role, and then run `azd deploy --all` instead of `azd up`. -The agent reads the toolbox's MCP endpoint from `TOOLBOX_ENDPOINT`. Create the toolbox once from the bundled [`toolbox.yaml`](toolbox.yaml): +#### Configure external inputs + +If init did not request these values, or you need to correct them, set them in the initialized +project's azd environment: ```bash -azd ai toolbox create agent-tools --from-file ./toolbox.yaml --project-endpoint https://.services.ai.azure.com/api/projects/ +azd env set GITHUB_MCP_PAT "" +azd env set AZURE_LANGUAGE_MCP_ENDPOINT "https://.cognitiveservices.azure.com/language/mcp?api-version=2025-11-15-preview" ``` -The first version becomes the default automatically. Use `azd ai toolbox list`, `azd ai toolbox show agent-tools`, and `azd ai toolbox version list agent-tools` to inspect, and `azd ai toolbox delete agent-tools --force` to remove it. +If you use GitHub Copilot for Azure to scaffold a hosted agent that consumes this toolbox, these +references describe the endpoint, authentication, and platform-header contract: -To stage incremental changes safely, use `azd ai toolbox connection add/remove` and `azd ai toolbox skill add/list/remove`; each creates a new toolbox version that carries forward existing connections and skills but **doesn't** change the default. Promote a version with `azd ai toolbox publish agent-tools ` when you're ready to make it active. +- [Toolbox overview](https://github.com/microsoft/GitHub-Copilot-for-Azure/blob/main/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/toolbox/toolbox.md) +- [Use toolbox in a hosted agent](https://github.com/microsoft/GitHub-Copilot-for-Azure/blob/main/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/use-toolbox-in-hosted-agent.md) -`azd ai toolbox create` prints the toolbox's versioned MCP endpoint. Copy that endpoint and store it in your `azd` environment so the agent connects to it: +#### Provision and deploy dependencies and agent ```bash -azd env set TOOLBOX_ENDPOINT "https://.services.ai.azure.com/api/projects//toolboxes/agent-tools/versions/1/mcp?api-version=v1" +azd up ``` -#### Provision Azure resources (if needed) - -If you don't already have a Foundry project and model deployment: +For a new Foundry project, run `azd provision`, grant the Language role to the new project identity, and +then run `azd deploy --all` instead of `azd up`. Otherwise `azd up` provisions the project/model as needed and deploys connections, toolbox, and agent in dependency +order. `azd provision` alone does not deploy the connections or toolbox. The versioned toolbox output +is mapped into the agent automatically; do not replace it with an unversioned endpoint or a +self-assignment. To inspect the deployed toolbox and endpoint: ```bash -azd provision +azd ai toolbox show agent-tools +azd ai toolbox versions list agent-tools +azd env get-value TOOLBOX_AGENT_TOOLS_MCP_ENDPOINT ``` #### Run the agent locally +Run `azd up` first. Local hosting does not create remote connections or the toolbox. Also authenticate +the local credential with `az login` (Azure CLI); `azd auth login` authenticates deployment commands. + ```bash +az login azd ai agent run ``` -The agent host will start on `http://localhost:8088`. - -#### Invoke the local agent - -In a separate terminal, from the project directory: +The agent host starts on `http://localhost:8088`. In a separate terminal, from the project root: ```bash azd ai agent invoke --local "What tools do you have?" ``` -#### Deploy to Foundry - -Once tested locally, deploy to Microsoft Foundry: - -```bash -azd deploy -``` - -For the full deployment guide, see [Deploy a hosted agent](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent). +#### Deploy changes to Foundry -#### Invoke the deployed agent +After editing tools, connections, or agent code, deploy every service so the agent receives the new +versioned toolbox endpoint: ```bash +azd deploy --all azd ai agent invoke "What tools do you have?" ``` +Edit the services in `azure.yaml` rather than mutating a separately managed toolbox. You do not need +to publish a default toolbox version for this agent because it uses the deployed version's endpoint. +See [Deploy a hosted agent](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent) +for the full deployment guide. + ### Option 2: VS Code (Foundry Toolkit) #### Prerequisites -1. **VS Code** with the **[Foundry Toolkit](https://learn.microsoft.com/en-us/azure/foundry/how-to/develop/get-started-projects-vs-code)** extension installed. +1. Install [VS Code with Foundry Toolkit](https://learn.microsoft.com/en-us/azure/foundry/how-to/develop/get-started-projects-vs-code) + and the [Python extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python). 2. Sign in to Azure in VS Code. -3. The `agent-tools` toolbox must exist in your Foundry project. Create it from the bundled [`toolbox.yaml`](toolbox.yaml) (`azd ai toolbox create agent-tools --from-file ./toolbox.yaml`) or in the Foundry portal before you run the agent. - -#### Create the project +3. Complete CLI initialization, external inputs, identity grants, and `azd up` above before local + testing. The Toolkit's agent-only deploy flow does not provision or update toolbox dependencies. -1. Open the Command Palette (`Ctrl+Shift+P`) and run **Foundry Toolkit: Create Hosted Agent**. -2. Select this sample from the gallery. The extension scaffolds the project into a new workspace and generates `agent.yaml`, `.env`, and `.vscode/tasks.json` + `launch.json` automatically. -3. Complete the **Foundry Project Setup** to pick the subscription and Foundry project (or create a new one). +#### Open the initialized project -#### Run and debug the agent +Open `my-toolbox-agent`, the directory containing `azure.yaml`, in VS Code. This sample uses the unified +configuration, not the legacy gallery flow that generates `agent.yaml`. Use the CLI template command +above to obtain this sample; do not initialize it through a legacy manifest gallery. -Press **F5** to start the agent in debug mode. The agent host will start on `http://localhost:8088`. +Create a Python 3.13 virtual environment in this directory: -#### Test with Agent Inspector +```bash +python -m venv .venv +``` -1. Open the Command Palette (`Ctrl+Shift+P`) and run **Foundry Toolkit: Open Agent Inspector**. -2. The Inspector connects to the running agent. Send messages to chat and view streamed responses. +Select `.venv` with **Python: Select Interpreter**, open a new VS Code terminal to activate it, then run: -#### Deploy to Foundry +```bash +python -m pip install --pre -r requirements.txt +``` -1. Open the Command Palette (`Ctrl+Shift+P`) and run **Foundry Toolkit: Deploy Hosted Agent**. The extension opens a **Deploy Hosted Agent** wizard and reads `agent.yaml` to auto-populate settings. -2. If prompted, complete **Foundry Project Setup** to select subscription and project. -3. On the **Basics** tab, choose deployment method (**Code** or **Container**) and confirm the agent name. -4. On **Review + Deploy**, confirm runtime details, pick **CPU and Memory** size, and click **Deploy**. -5. After deployment, invoke the agent in the Agent Playground and stream live logs from the **Logs** tab. +Copy [`.env.example`](.env.example) to `.env` and set the Foundry project endpoint, model deployment +name, and `TOOLBOX_ENDPOINT`. For the latter, use the output of +`azd env get-value TOOLBOX_AGENT_TOOLS_MCP_ENDPOINT`. Do not put the GitHub PAT in the agent's `.env`; +it belongs to the remote project connection. The agent also accepts `FOUNDRY_MODEL` as a local model override. -### Creating a Foundry Toolbox +#### Run and debug the agent -You can create a Foundry Toolbox by code. Refer to this sample for an example: [Foundry Toolbox CRUD Sample](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/samples/toolboxes/sample_toolboxes_crud.py). +Run `az login`, then start `python main.py` from the project root. To debug, open `main.py` and use +**Python Debugger: Debug Python File**. The host listens on `http://localhost:8088`. -You can also create a Foundry Toolbox in the Foundry portal. Read more about it [in the Foundry toolbox documentation](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/tools/toolbox). +Open the Command Palette (`Ctrl+Shift+P`), run **Foundry Toolkit: Open Agent Inspector**, and send +messages to the running agent. -## Troubleshooting +#### Deploy to Foundry -### A single failing MCP source can fail the whole agent +Run `azd deploy --all` from the initialized root to deploy connections, toolbox, and agent together. +The Toolkit's agent-only deploy wizard is not a replacement for this dependency deployment. -A toolbox aggregates every tool source behind one MCP endpoint. If **any** referenced MCP server fails while the toolbox enumerates tools (`tools/list`), the toolbox fails the entire enumeration, so the agent can't load its tools and every request returns an error (HTTP 500) until that source recovers. +## Finding the Entra audience for an MCP server -For example, a flaky third-party MCP source can intermittently return `HTTP 502 (Bad Gateway)` during enumeration, which surfaces as: +An Entra pass-through connection requires an **audience**, the Entra resource that the MCP server +validates tokens against. For Microsoft Foundry MCP, read its OAuth protected-resource metadata: +```bash +curl https://mcp.ai.azure.com/.well-known/oauth-protected-resource ``` -tools/list failed for 1 tool source(s), succeeded for 5 tool source(s) -{"errors":[{"name":"","type":"mcp","error":{"code":"HTTP_502", ...}}]} -``` -This is an upstream/service hiccup, not a problem with the agent code. Mitigations: +The `resource` value is `https://mcp.ai.azure.com`; use it as the audience. + +For connector-backed MCP servers such as Microsoft 365 / WorkIQ, the audience is published in the +Foundry Tools Catalog. Use the helper scripts in [`scripts/`](scripts/): +`./scripts/list-foundry-connectors.ps1 -ConnectorName ` or +`./scripts/list-foundry-connectors.sh -n `. Read `AzureActiveDirectoryResourceId` (equivalently +`resourceUri`) under `properties.x-ms-connection-parameters`. Omit the connector name to list connectors. + +## Troubleshooting + +### A single failing MCP source can fail the whole agent + +If any referenced MCP server fails during `tools/list`, the toolbox can fail the entire enumeration +and the agent cannot load its tools. A transient source failure can surface as HTTP 500 with an error +such as `tools/list failed for 1 tool source(s), succeeded for 5 tool source(s)`. -- Retry the request — these failures are usually transient. -- If a source is persistently unavailable, temporarily remove its tool entry (and connection) from `toolbox.yaml`, recreate the toolbox, and update `TOOLBOX_ENDPOINT`. -- Inspect deployed agent logs with `azd ai agent monitor` to identify which source failed. +Retry transient failures. For a persistently unavailable source, remove its tool entry and unused +connection service and `uses` dependency from `azure.yaml`, then run `azd deploy --all` to update the +toolbox and agent together. Inspect deployed logs with `azd ai agent monitor` to identify the failed source. ### Entra pass-through forwards the caller's identity -The Foundry MCP tool authenticates with **Entra pass-through** (`foundrymcpconn`): Foundry forwards the -calling user's Entra token to `https://mcp.ai.azure.com`. The token is forwarded both from the Foundry -portal **Agent Playground** (signed-in user) and by `azd ai agent invoke` (the developer's Entra token), -so the tools operate as that user and only act on resources the user can already access. The Foundry MCP -server requires no extra license — just access to the Foundry project. +The Foundry MCP connection forwards the calling user's Entra token to `https://mcp.ai.azure.com`. +The Foundry portal Agent Playground and `azd ai agent invoke` supply the developer's token, so tools +can act only on resources that user can access. Foundry MCP requires project access, not an extra license. -Because the tool acts as a specific user, running the agent **locally** (`python main.py`) or calling the -endpoint with a raw token uses whatever identity that token represents (`az login` user locally, the -agent's managed identity when hosted). If that identity has no access to the target resources, the tool -returns an authorization error even though it is discovered and called correctly. +Local authentication uses the `az login` account; hosted authentication uses managed identity and +platform request context. A credential or a fresh Python object does not grant resource permissions. +If the effective identity has no access, tools return authorization errors. -> Some other Entra pass-through MCP servers add their **own** entitlement checks on top of the token. For -> example, the Microsoft 365 / WorkIQ servers (Outlook Mail, Teams) require the caller to hold a -> **Microsoft 365 Copilot (Business Chat)** license; without it they fail with -> `WorkIQ license check failed. Required service plan(s): [M365_COPILOT_BUSINESS_CHAT]`. That is a -> property of those servers, not of Entra pass-through itself. +Other MCP servers can add their own entitlement checks. Microsoft 365 / WorkIQ servers, for example, +require a **Microsoft 365 Copilot (Business Chat)** license. That requirement belongs to those servers, +not to Entra pass-through itself. ## Next steps -- [Quickstart: Create a hosted agent](https://learn.microsoft.com/en-us/azure/foundry/agents/quickstarts/quickstart-hosted-agent) — end-to-end walkthrough using `azd` -- [Tool catalog](https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/tool-catalog) — browse available tools to extend your agent (Bing Search, Azure AI Search, file search, code interpreter, and more) -- [Manage hosted agents](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/manage-hosted-agent) — monitor and manage deployed agents -- [Basic agent](../basic/) — minimal agent with no tools -- [Add local tools](../tools/) — sample with locally-defined Python tool functions -- [Build multi-agent workflows](../workflows/) — sample with chained agent pipelines +- [Quickstart: Create a hosted agent](https://learn.microsoft.com/en-us/azure/foundry/agents/quickstarts/quickstart-hosted-agent) +- [Tool catalog](https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/tool-catalog) +- [Manage hosted agents](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/manage-hosted-agent) +- [Basic agent](../basic/README.md) +- [Add local tools](../tools/README.md) +- [Build multi-agent workflows](../workflows/README.md) diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/agent.manifest.yaml b/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/agent.manifest.yaml deleted file mode 100644 index 770bb0100ff..00000000000 --- a/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/agent.manifest.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: agent-framework-agent-with-foundry-toolbox-responses -description: > - An Agent Framework agent with Foundry Toolbox integration. -metadata: - tags: - - Agent Framework - - AI Agent Hosting - - Azure AI AgentServer - - Responses Protocol - - Streaming -template: - name: agent-framework-agent-with-foundry-toolbox-responses - kind: hosted - protocols: - - protocol: responses - version: 2.0.0 - environment_variables: - - name: AZURE_AI_MODEL_DEPLOYMENT_NAME - value: "{{AZURE_AI_MODEL_DEPLOYMENT_NAME}}" - - name: TOOLBOX_ENDPOINT - # Full MCP endpoint of the toolbox the agent consumes. Create the toolbox - # from the bundled toolbox.yaml, then copy the versioned endpoint it prints - # and store it in your azd environment before you run or deploy: - # azd ai toolbox create my-toolbox --from-file ./toolbox.yaml - # azd env set TOOLBOX_ENDPOINT "" - value: "{{TOOLBOX_ENDPOINT}}" -resources: - - kind: model - id: gpt-4.1 - name: AZURE_AI_MODEL_DEPLOYMENT_NAME diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/agent.yaml b/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/agent.yaml deleted file mode 100644 index 3711356799e..00000000000 --- a/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/agent.yaml +++ /dev/null @@ -1,13 +0,0 @@ -kind: hosted -name: agent-framework-agent-with-foundry-toolbox-responses -protocols: - - protocol: responses - version: 2.0.0 -resources: - cpu: "0.25" - memory: 0.5Gi -environment_variables: - - name: AZURE_AI_MODEL_DEPLOYMENT_NAME - value: ${AZURE_AI_MODEL_DEPLOYMENT_NAME} - - name: TOOLBOX_ENDPOINT - value: ${TOOLBOX_ENDPOINT} diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/azure.yaml b/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/azure.yaml new file mode 100644 index 00000000000..a67f90a7065 --- /dev/null +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/azure.yaml @@ -0,0 +1,122 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json + +requiredVersions: + azd: '>=1.32.0' + extensions: + azure.ai.agents: '>=1.0.0-beta.17' + azure.ai.connections: '>=1.0.0-beta.8' + azure.ai.toolboxes: '>=1.0.0-beta.8' +name: agent-framework-agent-with-foundry-toolbox-responses +services: + ai-project: + host: azure.ai.project + deployments: + - name: gpt-4.1 + model: + format: OpenAI + name: gpt-4.1 + version: '2025-04-14' + sku: + name: GlobalStandard + capacity: 10 + ghmcppat: + host: azure.ai.connection + uses: + - ai-project + category: RemoteTool + target: https://api.githubcopilot.com/mcp + authType: CustomKeys + credentials: + type: CustomKeys + keys: + Authorization: Bearer ${GITHUB_MCP_PAT} + env: + GITHUB_MCP_PAT: ${GITHUB_MCP_PAT} + langmcpconn: + host: azure.ai.connection + uses: + - ai-project + category: RemoteTool + target: ${AZURE_LANGUAGE_MCP_ENDPOINT} + authType: ProjectManagedIdentity + audience: https://cognitiveservices.azure.com/ + env: + AZURE_LANGUAGE_MCP_ENDPOINT: ${AZURE_LANGUAGE_MCP_ENDPOINT} + foundrymcpconn: + host: azure.ai.connection + uses: + - ai-project + category: RemoteTool + target: https://mcp.ai.azure.com + authType: UserEntraToken + audience: https://mcp.ai.azure.com + agent-tools: + host: azure.ai.toolbox + uses: + - ai-project + - ghmcppat + - langmcpconn + - foundrymcpconn + description: Multiple MCP servers behind one endpoint + env: + AZURE_LANGUAGE_MCP_ENDPOINT: ${AZURE_LANGUAGE_MCP_ENDPOINT} + tools: + - type: web_search + name: web_search + require_approval: never + - type: code_interpreter + name: code_interpreter + require_approval: never + - type: mcp + server_label: noauth_mcp + server_url: https://gitmcp.io/Azure/azure-rest-api-specs + require_approval: never + - type: mcp + server_label: github + server_url: https://api.githubcopilot.com/mcp + connection: ghmcppat + require_approval: never + - type: mcp + server_label: language-mcp + server_url: ${AZURE_LANGUAGE_MCP_ENDPOINT} + connection: langmcpconn + require_approval: never + - type: mcp + server_label: foundry-mcp + server_url: https://mcp.ai.azure.com + connection: foundrymcpconn + require_approval: never + agent-framework-agent-with-foundry-toolbox-responses: + host: azure.ai.agent + project: . + language: python + codeConfiguration: + dependencyResolution: remote_build + runtime: python_3_13 + entryPoint: main.py + uses: + - ai-project + - agent-tools + kind: hosted + name: agent-framework-agent-with-foundry-toolbox-responses + description: An Agent Framework agent with Foundry Toolbox integration. + metadata: + tags: + - Agent Framework + - AI Agent Hosting + - Azure AI AgentServer + - Responses Protocol + - Streaming + protocols: + - protocol: responses + version: 2.0.0 + env: + FOUNDRY_PROJECT_ENDPOINT: ${FOUNDRY_PROJECT_ENDPOINT} + AZURE_AI_MODEL_DEPLOYMENT_NAME: ${AZURE_AI_MODEL_DEPLOYMENT_NAME} + TOOLBOX_ENDPOINT: ${TOOLBOX_AGENT_TOOLS_MCP_ENDPOINT} + container: + resources: + cpu: '0.25' + memory: 0.5Gi +infra: + provider: microsoft.foundry diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/tests/test_configuration.py b/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/tests/test_configuration.py new file mode 100644 index 00000000000..2ce3690d6b7 --- /dev/null +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/tests/test_configuration.py @@ -0,0 +1,141 @@ +# Copyright (c) Microsoft. All rights reserved. + +from __future__ import annotations + +import re +from graphlib import TopologicalSorter +from pathlib import Path + +import yaml + +SAMPLE_ROOT = Path(__file__).parents[1] +AGENT_NAME = "agent-framework-agent-with-foundry-toolbox-responses" + + +def _configuration() -> dict: + return yaml.safe_load((SAMPLE_ROOT / "azure.yaml").read_text(encoding="utf-8")) + + +def test_hosted_agent_preserves_metadata_protocol_and_resources() -> None: + configuration = _configuration() + agent = configuration["services"][AGENT_NAME] + + assert configuration["name"] == agent["name"] == AGENT_NAME + assert agent["kind"] == "hosted" + assert agent["description"] == "An Agent Framework agent with Foundry Toolbox integration." + assert agent["metadata"]["tags"] == [ + "Agent Framework", + "AI Agent Hosting", + "Azure AI AgentServer", + "Responses Protocol", + "Streaming", + ] + assert agent["protocols"] == [{"protocol": "responses", "version": "2.0.0"}] + assert agent["container"]["resources"] == {"cpu": "0.25", "memory": "0.5Gi"} + assert configuration["infra"]["provider"] == "microsoft.foundry" + + +def test_flat_source_layout_is_independently_deployable() -> None: + agent = _configuration()["services"][AGENT_NAME] + + assert agent["project"] == "." + assert agent["language"] == "python" + assert agent["codeConfiguration"] == { + "dependencyResolution": "remote_build", + "runtime": "python_3_13", + "entryPoint": "main.py", + } + assert (SAMPLE_ROOT / agent["project"] / agent["codeConfiguration"]["entryPoint"]).is_file() + assert (SAMPLE_ROOT / agent["project"] / "requirements.txt").is_file() + + +def test_model_and_runtime_environment_bindings() -> None: + services = _configuration()["services"] + deployment = services["ai-project"]["deployments"][0] + + assert deployment["name"] == deployment["model"]["name"] == "gpt-4.1" + assert deployment["model"]["format"] == "OpenAI" + assert isinstance(deployment["model"]["version"], str) + assert services[AGENT_NAME]["env"] == { + "FOUNDRY_PROJECT_ENDPOINT": "${FOUNDRY_PROJECT_ENDPOINT}", + "AZURE_AI_MODEL_DEPLOYMENT_NAME": "${AZURE_AI_MODEL_DEPLOYMENT_NAME}", + "TOOLBOX_ENDPOINT": "${TOOLBOX_AGENT_TOOLS_MCP_ENDPOINT}", + } + + +def test_dependency_graph_orders_project_connections_toolbox_and_agent() -> None: + services = _configuration()["services"] + graph = {name: service.get("uses", []) for name, service in services.items()} + + assert all(dependency in services for dependencies in graph.values() for dependency in dependencies) + order = list(TopologicalSorter(graph).static_order()) + assert order[0] == "ai-project" + assert order[-1] == AGENT_NAME + for connection in ("ghmcppat", "langmcpconn", "foundrymcpconn"): + assert order.index(connection) < order.index("agent-tools") + assert services[connection]["uses"] == ["ai-project"] + assert services[AGENT_NAME]["uses"] == ["ai-project", "agent-tools"] + + +def test_six_tool_sources_use_declared_connections() -> None: + services = _configuration()["services"] + tools = services["agent-tools"]["tools"] + + assert [tool["type"] for tool in tools] == ["web_search", "code_interpreter", "mcp", "mcp", "mcp", "mcp"] + assert all(tool["require_approval"] == "never" for tool in tools) + mcp_tools = {tool["server_label"]: tool for tool in tools if tool["type"] == "mcp"} + assert mcp_tools["noauth_mcp"]["server_url"] == "https://gitmcp.io/Azure/azure-rest-api-specs" + assert "connection" not in mcp_tools["noauth_mcp"] + for label, connection in ( + ("github", "ghmcppat"), + ("language-mcp", "langmcpconn"), + ("foundry-mcp", "foundrymcpconn"), + ): + assert mcp_tools[label]["connection"] == connection + assert connection in services["agent-tools"]["uses"] + assert mcp_tools[label]["server_url"] == services[connection]["target"] + assert "project_connection_id" not in mcp_tools[label] + + +def test_authentication_and_external_inputs_are_forwarded_without_embedded_secrets() -> None: + services = _configuration()["services"] + + assert services["ghmcppat"]["authType"] == "CustomKeys" + assert services["ghmcppat"]["credentials"] == { + "type": "CustomKeys", + "keys": {"Authorization": "Bearer ${GITHUB_MCP_PAT}"}, + } + assert services["langmcpconn"]["authType"] == "ProjectManagedIdentity" + assert services["langmcpconn"]["audience"] == "https://cognitiveservices.azure.com/" + assert services["foundrymcpconn"]["authType"] == "UserEntraToken" + assert services["foundrymcpconn"]["audience"] == "https://mcp.ai.azure.com" + assert "GITHUB_MCP_PAT" not in services[AGENT_NAME]["env"] + for service in services.values(): + payload = {key: value for key, value in service.items() if key != "env"} + references = re.findall(r"\$\{([^}]+)\}", yaml.safe_dump(payload)) + assert all(reference in service.get("env", {}) for reference in references) + + +def test_readme_uses_own_template_and_existing_local_links() -> None: + readme = (SAMPLE_ROOT / "README.md").read_text(encoding="utf-8") + + assert ( + "--template https://github.com/microsoft/agent-framework/blob/main/" + "python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/azure.yaml --src ." + ) in readme + assert readme.index("Create a fine-grained personal access token") < readme.index("azd ai agent init --template") + assert "--model-deployment " in readme + assert "https://github.com/Azure/azure-dev/issues/10519" in readme + assert "azd deploy --all" in readme + for target in re.findall(r"\]\(([^)]+)\)", readme): + if not target.startswith(("https://", "http://", "#")): + assert (SAMPLE_ROOT / target.split("#", 1)[0]).exists(), target + + +def test_legacy_definitions_are_retired_and_local_secrets_excluded() -> None: + for filename in ("agent.manifest.yaml", "agent.yaml", "toolbox.yaml"): + assert not (SAMPLE_ROOT / filename).exists() + for filename in (".azdignore", ".dockerignore"): + patterns = (SAMPLE_ROOT / filename).read_text(encoding="utf-8").splitlines() + assert ".env" in patterns + assert ".azure" in patterns diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/toolbox.yaml b/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/toolbox.yaml deleted file mode 100644 index b0102938f0d..00000000000 --- a/python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/toolbox.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# toolbox.yaml -# Defines the toolbox this agent consumes. Create it once with: -# azd ai toolbox create agent-tools --from-file ./toolbox.yaml -# After creating it, copy the versioned MCP endpoint the command prints and -# set it as the TOOLBOX_ENDPOINT environment variable. The agent connects to -# that endpoint at runtime. -description: Multiple MCP servers behind one endpoint -tools: - - type: web_search - name: web_search - require_approval: "never" - - type: code_interpreter - name: code_interpreter - require_approval: "never" - - type: mcp - # This MCP tool doesn't require authentication - server_label: noauth_mcp - server_url: "https://gitmcp.io/Azure/azure-rest-api-specs" - require_approval: "never" - - type: mcp - # This MCP tool uses the GitHub MCP server with a PAT for authentication or OAuth2 - server_label: github - project_connection_id: ghmcppat # use `ghmcpoauth` for OAuth2 authentication - require_approval: "never" - - type: mcp - # This MCP tool uses the Azure Language MCP server with agent identity for authentication - server_label: language-mcp - project_connection_id: langmcpconn - require_approval: "never" - - type: mcp - # This MCP tool uses the Microsoft Foundry MCP server with Entra pass-through (user token) for authentication - server_label: foundry-mcp - project_connection_id: foundrymcpconn - require_approval: "never"