Skip to content
Open
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
@@ -0,0 +1,7 @@
.venv
__pycache__
*.pyc
*.pyo
*.pyd
.Python
.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FOUNDRY_PROJECT_ENDPOINT="..."
AZURE_AI_MODEL_DEPLOYMENT_NAME="..."
TOOLBOX_NAME="..."
Comment thread
SergeyMenshykh marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM python:3.12-slim

RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY . user_agent/
WORKDIR /app/user_agent

RUN if [ -f requirements.txt ]; then \
pip install -r requirements.txt; \
else \
echo "No requirements.txt found"; \
fi

EXPOSE 8088

CMD ["python", "main.py"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# What this sample demonstrates

An [Agent Framework](https://github.com/microsoft/agent-framework) agent that discovers **MCP-based skills from a Foundry Toolbox** and makes them available via `SkillsProvider(MCPSkillsSource(...))`, hosted using the **Responses protocol**.

The `SkillsProvider` is attached to the agent as a context provider and implements the [Agent Skills](https://agentskills.io/) progressive-disclosure pattern. When the agent is prompted, it discovers available skills in the Foundry Toolbox via the provider:

1. **Advertise** — skill names and descriptions are injected into the system prompt so the agent knows what is available.
2. **Load** — when the agent decides a skill is relevant, it retrieves the full skill body with detailed instructions via the provider.
3. **Read resources** — if a skill includes supplementary content (reference documents, assets), the agent reads them on demand via the provider.

This way the full skill body and resources are only loaded when the agent actually needs them, reducing token usage.

## How It Works

### 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 the `mcp` library's `streamable_http_client`, discovers skills served by the toolbox through `MCPSkillsSource`, and injects them as a context provider via `SkillsProvider`. The toolbox endpoint URL is derived from `FOUNDRY_PROJECT_ENDPOINT` and `TOOLBOX_NAME`.

See [main.py](main.py) for the full implementation.

### Agent Hosting

The agent is hosted using the [Agent Framework](https://github.com/microsoft/agent-framework) with the `ResponsesHostServer`, which provisions a REST API endpoint compatible with the OpenAI Responses protocol.

## Prerequisites

- Python 3.12+
- An Azure AI Foundry project with a deployed model (e.g., `gpt-5`)
- A Foundry Toolbox with skills attached (see below)
- Azure CLI logged in (`az login`)

## Setting up a Foundry Toolbox with skills

This sample requires a Foundry Toolbox that has skills attached to it. Skills are `SKILL.md` files you author once, store centrally in Foundry through the versioned Skills API, and attach to a toolbox so any MCP client can discover and load them.

1. **Author a skill** — Create a `SKILL.md` file following the [Agent Skills](https://agentskills.io/) specification format (YAML front matter with `name` and `description`, plus Markdown body).
2. **Create the skill in Foundry** — Upload the skill via the Skills REST API, Python SDK, or `azd ai skill create`. See [Use skills with Microsoft Foundry agents](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/tools/skills).
3. **Attach the skill to a toolbox** — Add a skill reference to a toolbox version so MCP clients can discover it. See [Attach skills to a toolbox](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/tools/toolbox#attach-skills-to-a-toolbox).

When the agent connects to the toolbox MCP endpoint, skills are advertised through a well-known `skill://index.json` discovery resource. The `MCPSkillsSource` in this sample reads `skill://index.json` the first time the agent runs to discover all attached skills, then fetches each `SKILL.md` body on demand via `resources/read`.

## Running the Agent Host

Follow the instructions in the [Running the Agent Host Locally](../../README.md#running-the-agent-host-locally) section of the README in the parent directory to run the agent host.

An extra environment variable must be set to point to the toolbox name:

```bash
export TOOLBOX_NAME="my-toolbox"
```

Or in PowerShell:

```powershell
$env:TOOLBOX_NAME="my-toolbox"
```

You can also place these in a `.env` file next to `main.py` — see [`.env.example`](.env.example).

## Interacting with the agent

> Depending on how you run the agent host, you can invoke the agent using `curl` (`Invoke-WebRequest` in PowerShell) or `azd`. Please refer to the [parent README](../../README.md) for more details. Use this README for sample queries you can send to the agent.

Send a POST request to the server with a JSON body containing an `"input"` field to interact with the agent. For example:

```bash
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "What skills do you have available?"}'
```

## Deploying the Agent to Foundry

To host the agent on Foundry, follow the instructions in the [Deploying the Agent to Foundry](../../README.md#deploying-the-agent-to-foundry) section of the README in the parent directory.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: hosted-toolbox-mcp-skills
displayName: "Hosted Toolbox MCP Skills Agent"

description: >
A hosted agent that discovers MCP-based skills from a Foundry Toolbox
and makes them available to the agent via the agent skills provider.

metadata:
tags:
- AI Agent Hosting
- Azure AI AgentServer
- Responses Protocol
- Agent Framework
- MCP
- Model Context Protocol
- Agent Skills
- Foundry Toolbox
- Foundry Toolbox Skills

template:
name: hosted-toolbox-mcp-skills
kind: hosted
protocols:
- protocol: responses
version: 1.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_NAME
value: "{{TOOLBOX_NAME}}"
parameters:
properties:
- name: TOOLBOX_NAME
secret: false
description: Name of the Foundry Toolbox to connect to for MCP skill discovery
resources:
- kind: model
id: gpt-5
name: AZURE_AI_MODEL_DEPLOYMENT_NAME
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# yaml-language-server: $schema=https://github.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml
kind: hosted
name: hosted-toolbox-mcp-skills
protocols:
- protocol: responses
version: 1.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_NAME
value: ${TOOLBOX_NAME}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Copyright (c) Microsoft. All rights reserved.

import asyncio
import os
from collections.abc import Callable, Generator

import httpx
from agent_framework import Agent, MCPSkillsSource, SkillsProvider
from agent_framework.foundry import FoundryChatClient
from agent_framework_foundry_hosting import ResponsesHostServer
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from dotenv import load_dotenv
from mcp.client.session import ClientSession
from mcp.client.streamable_http import streamable_http_client

# Load environment variables from .env file
load_dotenv()


class ToolboxAuth(httpx.Auth):
"""Attach a fresh Foundry bearer token to every request."""

def __init__(self, token_provider: Callable[[], str]):
self._get_token = token_provider

def auth_flow(self, request: httpx.Request) -> Generator[httpx.Request, httpx.Response, None]:
request.headers["Authorization"] = f"Bearer {self._get_token()}"
yield request


async def main() -> None:
project_endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
deployment = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"]
toolbox_name = os.environ["TOOLBOX_NAME"]

# Build the Toolbox MCP URL from the project endpoint and toolbox name.
toolbox_mcp_url = f"{project_endpoint.rstrip('/')}/toolboxes/{toolbox_name}/mcp?api-version=v1"

Comment thread
SergeyMenshykh marked this conversation as resolved.
credential = DefaultAzureCredential()

# Create a token provider for Foundry bearer auth
token_provider = get_bearer_token_provider(credential, "https://ai.azure.com/.default")

# ── Connect to the Foundry Toolbox MCP endpoint ──────────────────────────
# Create an HTTP client that attaches a fresh Foundry bearer token to every
# request and advertises the toolbox preview feature flag.
async with (
httpx.AsyncClient(
auth=ToolboxAuth(token_provider),
headers={"Foundry-Features": "Toolboxes=V1Preview"},
timeout=httpx.Timeout(30.0, read=300.0),
follow_redirects=True,
) as http_client,
streamable_http_client(
url=toolbox_mcp_url,
http_client=http_client,
) as (read, write, _),
ClientSession(read, write) as session,
):
await session.initialize()

print(f"Connected to Foundry Toolbox '{toolbox_name}' MCP server.")

# ── Configure MCP-based skills provider ──────────────────────────────
# MCPSkillsSource reads skill://index.json and creates one MCPSkill per
Comment thread
SergeyMenshykh marked this conversation as resolved.
# skill-md entry; SKILL.md bodies are fetched on demand via
# resources/read.
skills_provider = SkillsProvider(MCPSkillsSource(client=session))

# ── Create the agent ─────────────────────────────────────────────────
client = FoundryChatClient(
project_endpoint=project_endpoint,
model=deployment,
credential=credential,
)

agent = Agent(
client=client,
name=os.environ.get("AGENT_NAME", "hosted-toolbox-mcp-skills"),
instructions="You are a helpful assistant.",
context_providers=[skills_provider],
# 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
default_options={"store": False},
)

# ── Build and run the host ───────────────────────────────────────────
server = ResponsesHostServer(agent)
await server.run_async()


if __name__ == "__main__":
asyncio.run(main())
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
agent-framework
agent-framework-foundry-hosting

mcp>=1.24.0,<2
Loading