Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ You can also create a Foundry Toolbox in the Foundry portal. Read more about it

> If you set up a project with this sample and provision the resources using `azd provision`, a Foundry Toolbox will be created with the specified tools in [`agent.manifest.yaml`](agent.manifest.yaml).

### 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**: 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**: 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)**: The tool requires OAuth2 to authenticate. Sample MCP server: `https://api.githubcopilot.com/mcp` (GitHub MCP server) with OAuth2 for authentication.
- **Agent identity authentication**: 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) with agent identity for authentication.
- **Entra Pass-through authentication**: The tool requires an Entra pass-through token to authenticate. Sample MCP server: Microsoft Outlook MCP server with Entra pass-through for authentication.

> Definitions of these authentication methods can be found in the [agent.manifest.yaml](agent.manifest.yaml) file in this sample.

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/tree/main/samples/python/toolbox/azd#supported-scenarios).

## How It Works

### Model Integration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,88 @@ template:
value: "{{AZURE_AI_MODEL_DEPLOYMENT_NAME}}"
- name: TOOLBOX_NAME
value: "agent-tools"
parameters:
properties:
- name: mcp_endpoint
# `azd ai agent init -m` will prompt for this value when initializing the agent manifest
secret: false
description: URL of the public MCP server (e.g. https://gitmcp.io/Azure/azure-rest-api-specs) that does not require authentication
- name: github_pat
# `azd ai agent init -m` will prompt for this value when initializing the agent manifest
secret: true
description: GitHub Personal Access Token used to authenticate with the GitHub MCP server (press Enter if OAuth2 is used instead)
- name: language_mcp_entra_audience
secret: false
description: Entra ID audience for the Azure Language MCP server (e.g. https://cognitiveservices.azure.com/)
- name: language_mcp_target_url
secret: false
description: URL of the Azure Language MCP server that accepts agent identity tokens (e.g. https://{foundry-resource-name}.cognitiveservices.azure.com/language/mcp?api-version=2025-11-15-preview)
- name: outlook_mail_entra_audience
secret: false
description: Entra ID audience for the Outlook Mail MCP server
- name: outlook_mail_entra_mcp_target
secret: false
description: URL of the Outlook Mail MCP server that accepts user Entra tokens
resources:
- kind: model
id: gpt-4.1-mini
name: AZURE_AI_MODEL_DEPLOYMENT_NAME
- kind: connection
# A connection that uses a GitHub Personal Access Token (PAT) to authenticate with the GitHub MCP server
name: github-mcp-pat-conn
category: RemoteTool
authType: CustomKeys
target: https://api.githubcopilot.com/mcp
credentials:
type: CustomKeys
keys:
Authorization: "Bearer {{ github_pat }}"
- kind: connection
# A connection that uses OAuth2 to authenticate with the GitHub MCP server
name: github-mcp-oauth-conn
category: RemoteTool
authType: OAuth2
target: https://api.githubcopilot.com/mcp
connectorName: foundrygithubmcp
credentials:
type: OAuth2
clientId: managed
clientSecret: managed
- kind: connection
name: language-mcp-conn
category: RemoteTool
authType: AgenticIdentity
audience: "{{ language_mcp_entra_audience }}"
target: "{{ language_mcp_target_url }}"
- kind: connection
name: outlook-mail-conn
category: RemoteTool
authType: UserEntraToken
audience: "{{ outlook_mail_entra_audience }}"
target: "{{ outlook_mail_entra_mcp_target }}"
- kind: toolbox
name: agent-tools
tools:
- type: web_search
name: web_search
- type: code_interpreter
name: code_interpreter

- type: mcp
# This MCP tool doesn't require authentication
server_label: noauth_mcp
server_url: "{{ mcp_endpoint }}"
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: github-mcp-pat-conn # use `github-mcp-oauth-conn` for OAuth2 authentication
Comment on lines +94 to +96
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: language-mcp-conn
require_approval: "never"
- type: mcp
server_label: outlook-mail
project_connection_id: outlook-mail-conn
require_approval: "never"
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@
import asyncio
import os
from collections.abc import Callable
from typing import Any

import httpx
from agent_framework import Agent, MCPStreamableHTTPTool
from agent_framework.foundry import FoundryChatClient
from agent_framework_foundry_hosting import ResponsesHostServer
from azure.core.credentials import TokenCredential
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()


def _resolve_toolbox_endpoint() -> str:
def resolve_toolbox_endpoint() -> str:
"""Resolve the toolbox MCP endpoint URL.

Prefers the explicit ``FOUNDRY_TOOLBOX_ENDPOINT`` env var; falls back to
Expand All @@ -29,42 +28,50 @@ def _resolve_toolbox_endpoint() -> str:
return endpoint
project_endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"].rstrip("/")
toolbox_name = os.environ["TOOLBOX_NAME"]
return f"{project_endpoint}/toolsets/{toolbox_name}/mcp?api-version=v1"
return f"{project_endpoint}/toolboxes/{toolbox_name}/mcp?api-version=v1"
Comment on lines 29 to +31


def make_toolbox_header_provider(credential: TokenCredential) -> Callable[[dict[str, Any]], dict[str, str]]:
"""Build a header_provider that injects a fresh Azure AI bearer token on every MCP request."""
get_token = get_bearer_token_provider(credential, "https://ai.azure.com/.default")
class ToolboxAuth(httpx.Auth):
"""Injects a fresh bearer token on every request."""

def provide(_kwargs: dict[str, Any]) -> dict[str, str]:
return {
"Authorization": f"Bearer {get_token()}",
}
def __init__(self, token_provider: Callable[[], str]):
self._get_token = token_provider

return provide
def auth_flow(self, request: httpx.Request):
request.headers["Authorization"] = f"Bearer {self._get_token()}"
yield request


async def main():
credential = DefaultAzureCredential()

# Create the toolbox
token_provider = get_bearer_token_provider(credential, "https://ai.azure.com/.default")

http_client = httpx.AsyncClient(
auth=ToolboxAuth(token_provider),
headers={"Foundry-Features": "Toolboxes=V1Preview"},
timeout=120.0,
)
Comment on lines +51 to +55

toolbox = MCPStreamableHTTPTool(
name=os.environ.get("TOOLBOX_NAME", "toolbox"),
url=resolve_toolbox_endpoint(),
Comment on lines +58 to +59
http_client=http_client,
load_prompts=False,
)

# Create the chat client
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=credential,
)

toolbox_tool = MCPStreamableHTTPTool(
name="foundry_toolbox",
description="Tools exposed by the configured Foundry toolbox",
url=_resolve_toolbox_endpoint(),
header_provider=make_toolbox_header_provider(credential),
load_prompts=False,
)

async with Agent(
client=client,
instructions="You are a friendly assistant. Keep your answers brief.",
tools=toolbox_tool,
tools=toolbox,
# History will be managed by the hosting infrastructure, thus there
# is no need to store history by the service. Learn more at:
# https://developers.openai.com/api/reference/resources/responses/methods/create
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ This agent uses four tools:
1. **Get Current Working Directory Tool (`get_cwd`)** – Returns the current working directory of the agent host process.
2. **List Files Tool (`list_files`)** – Lists the files in a specified directory.
3. **Read File Tool (`read_file`)** – Reads the contents of a specified file.
4. **Code Interpreter Tool (`code_interpreter`)** – Allows the agent to execute Python code in a safe.
4. **Code Interpreter Tool (`code_interpreter`)** – Allows the agent to execute Python code in a safe sandboxed environment.
5. **Web Search Tool (`web_search`)** – Allows the agent to perform web searches using the Bing Search API.

> In this sample, the filesystem tools are function tools defined in Python using the `@tool` decorator from the Agent Framework. The code interpreter tool is a managed tool provided by [Foundry Toolbox](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/tools/toolbox). Learn more about foundry toolbox integration with hosted agents with this [sample](../04_foundry_toolbox/).
> In this sample, the filesystem tools are function tools defined in Python using the `@tool` decorator from the Agent Framework. The code interpreter tool and web search tool are managed tools provided by [Foundry Toolbox](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/tools/toolbox). Learn more about foundry toolbox integration with hosted agents with this [sample](../04_foundry_toolbox/).

## Running the Agent Host

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@
import asyncio
import os
from collections.abc import Callable
from typing import Any

import httpx
from agent_framework import Agent, MCPStreamableHTTPTool, tool
from agent_framework.foundry import FoundryChatClient
from agent_framework_foundry_hosting import ResponsesHostServer
from azure.core.credentials import TokenCredential
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()


def _resolve_toolbox_endpoint() -> str:
def resolve_toolbox_endpoint() -> str:
"""Resolve the toolbox MCP endpoint URL.

Prefers the explicit ``FOUNDRY_TOOLBOX_ENDPOINT`` env var; falls back to
Expand All @@ -29,19 +28,18 @@ def _resolve_toolbox_endpoint() -> str:
return endpoint
project_endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"].rstrip("/")
toolbox_name = os.environ["TOOLBOX_NAME"]
return f"{project_endpoint}/toolsets/{toolbox_name}/mcp?api-version=v1"
return f"{project_endpoint}/toolboxes/{toolbox_name}/mcp?api-version=v1"


def make_toolbox_header_provider(credential: TokenCredential) -> Callable[[dict[str, Any]], dict[str, str]]:
"""Build a header_provider that injects a fresh Azure AI bearer token on every MCP request."""
get_token = get_bearer_token_provider(credential, "https://ai.azure.com/.default")
class ToolboxAuth(httpx.Auth):
"""Injects a fresh bearer token on every request."""

def provide(_kwargs: dict[str, Any]) -> dict[str, str]:
return {
"Authorization": f"Bearer {get_token()}",
}
def __init__(self, token_provider: Callable[[], str]):
self._get_token = token_provider

return provide
def auth_flow(self, request: httpx.Request):
request.headers["Authorization"] = f"Bearer {self._get_token()}"
yield request


@tool(description="Get the current working directory.", approval_mode="never_require")
Expand Down Expand Up @@ -75,34 +73,37 @@ def read_file(file_path: str) -> str:
async def main():
credential = DefaultAzureCredential()

# Create the toolbox
token_provider = get_bearer_token_provider(credential, "https://ai.azure.com/.default")

http_client = httpx.AsyncClient(
auth=ToolboxAuth(token_provider),
headers={"Foundry-Features": "Toolboxes=V1Preview"},
timeout=120.0,
)
Comment on lines +79 to +83

toolbox = MCPStreamableHTTPTool(
name=os.environ.get("TOOLBOX_NAME", "toolbox"),
url=resolve_toolbox_endpoint(),
http_client=http_client,
load_prompts=False,
)

# Create the chat client
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=credential,
)

# Connect to the toolbox MCP endpoint and expose only the code_interpreter tool.
# The toolbox deployed has two tools: (see agent.manifest.yaml)
# - `code_interpreter`
# - `web_search`
# We only need the `code_interpreter` tool for this sample.
toolbox_tool = MCPStreamableHTTPTool(
name="foundry_toolbox",
description="Tools exposed by the configured Foundry toolbox",
url=_resolve_toolbox_endpoint(),
header_provider=make_toolbox_header_provider(credential),
load_prompts=False,
allowed_tools=["code_interpreter"],
)

async with Agent(
client=client,
instructions=(
"You are a friendly assistant. Keep your answers brief. "
"Make sure all mathematical calculations are performed using the code interpreter "
"instead of mental arithmetic."
),
tools=[get_cwd, list_files, read_file, toolbox_tool],
tools=[get_cwd, list_files, read_file, toolbox],
# History will be managed by the hosting infrastructure, thus there
# is no need to store history by the service. Learn more at:
# https://developers.openai.com/api/reference/resources/responses/methods/create
Expand Down
Loading