Skip to content

Commit caa75f7

Browse files
SergeyMenshykhsemenshiCopilotCopilot
authored
Python: Add Foundry Toolbox MCP skills hosted agent sample (#6363)
* Add 12_foundry_toolbox_mcp_skills hosted agent sample Demonstrates using MCPSkillsSource with a Foundry Toolbox MCP endpoint to discover and serve skills via SkillsProvider (progressive disclosure). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix env var reference in README and reuse local var in main.py Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Require AZURE_AI_MODEL_DEPLOYMENT_NAME and use placeholder in .env.example Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Document Toolbox MCP skills vs Foundry Skills in sample README Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Reference 12_foundry_toolbox_mcp_skills in parent README Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: SergeyMenshykh <SergeMenshikh@outlook.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent cfb033e commit caa75f7

9 files changed

Lines changed: 264 additions & 1 deletion

File tree

python/samples/04-hosting/foundry-hosted-agents/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ This directory contains samples that demonstrate how to use hosted [Agent Framew
1919
| 9 | [Foundry Skills](responses/09_foundry_skills/) | An agent that uploads `SKILL.md` files to the Foundry Skills REST API and downloads them at startup, decoupling tone/policy guidelines from agent code. |
2020
| 10 | [Foundry Memory](responses/10_foundry_memory/) | An agent with persistent semantic memory backed by an Azure AI Foundry Memory Store, using `FoundryMemoryProvider` to remember user facts across sessions. |
2121
| 11 | [Monty CodeAct](responses/11_monty_codeact/) | An agent with a Monty-backed CodeAct context provider, exposing a single `execute_code` tool that runs Python in a [pydantic-monty](https://github.com/pydantic/monty) interpreter and invokes typed host tools (`compute`, `fetch_data`) from inside the sandbox. Uses the alpha `agent-framework-monty` package. |
22-
| 12 | [Using deployed agent](responses/using_deployed_agent.py) | A sample demonstrating how to invoke an agent that has already been deployed to Foundry, showing how to interact with a hosted agent in code. |
22+
| 12 | [Foundry Toolbox MCP Skills](responses/12_foundry_toolbox_mcp_skills/) | An agent that discovers MCP-based skills attached to a Foundry Toolbox and serves them via `SkillsProvider(MCPSkillsSource(...))`, fetching `SKILL.md` bodies and supplementary resources on demand. |
23+
| 13 | [Using deployed agent](responses/using_deployed_agent.py) | A sample demonstrating how to invoke an agent that has already been deployed to Foundry, showing how to interact with a hosted agent in code. |
2324

2425
### Invocations API
2526

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.venv
2+
__pycache__
3+
*.pyc
4+
*.pyo
5+
*.pyd
6+
.Python
7+
.env
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
FOUNDRY_PROJECT_ENDPOINT="..."
2+
AZURE_AI_MODEL_DEPLOYMENT_NAME="..."
3+
TOOLBOX_NAME="..."
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
FROM python:3.12-slim
2+
3+
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
4+
5+
WORKDIR /app
6+
7+
COPY . user_agent/
8+
WORKDIR /app/user_agent
9+
10+
RUN if [ -f requirements.txt ]; then \
11+
pip install -r requirements.txt; \
12+
else \
13+
echo "No requirements.txt found"; \
14+
fi
15+
16+
EXPOSE 8088
17+
18+
CMD ["python", "main.py"]
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# What this sample demonstrates
2+
3+
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**.
4+
5+
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:
6+
7+
1. **Advertise** — skill names and descriptions are injected into the system prompt so the agent knows what is available.
8+
2. **Load** — when the agent decides a skill is relevant, it retrieves the full skill body with detailed instructions via the provider.
9+
3. **Read resources** — if a skill includes supplementary content (reference documents, assets), the agent reads them on demand via the provider.
10+
11+
This way the full skill body and resources are only loaded when the agent actually needs them, reducing token usage.
12+
13+
## Toolbox MCP skills vs. Foundry Skills
14+
15+
Foundry exposes skills in two ways, and this sample uses the second one.
16+
17+
Foundry Skills are managed through the Foundry Skills REST API. An agent downloads each `SKILL.md` as a ZIP at startup, serving the bodies from local files. See the [`09_foundry_skills`](../09_foundry_skills/README.md) sample for a demonstration.
18+
19+
Toolbox MCP skills are accessed through a toolbox over the MCP protocol. A toolbox bundles a curated set of skills behind one MCP endpoint, and any MCP client discovers them automatically. Skill bodies and any supplementary resources are fetched on demand.
20+
21+
## How It Works
22+
23+
### Model Integration
24+
25+
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`.
26+
27+
See [main.py](main.py) for the full implementation.
28+
29+
### Agent Hosting
30+
31+
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.
32+
33+
## Prerequisites
34+
35+
- Python 3.12+
36+
- An Azure AI Foundry project with a deployed model (e.g., `gpt-5`)
37+
- A Foundry Toolbox with skills attached (see below)
38+
- Azure CLI logged in (`az login`)
39+
40+
## Setting up a Foundry Toolbox with skills
41+
42+
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.
43+
44+
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).
45+
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).
46+
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).
47+
48+
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`.
49+
50+
## Running the Agent Host
51+
52+
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.
53+
54+
An extra environment variable must be set to point to the toolbox name:
55+
56+
```bash
57+
export TOOLBOX_NAME="my-toolbox"
58+
```
59+
60+
Or in PowerShell:
61+
62+
```powershell
63+
$env:TOOLBOX_NAME="my-toolbox"
64+
```
65+
66+
You can also place these in a `.env` file next to `main.py` — see [`.env.example`](.env.example).
67+
68+
## Interacting with the agent
69+
70+
> 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.
71+
72+
Send a POST request to the server with a JSON body containing an `"input"` field to interact with the agent. For example:
73+
74+
```bash
75+
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "What skills do you have available?"}'
76+
```
77+
78+
## Deploying the Agent to Foundry
79+
80+
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.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: hosted-toolbox-mcp-skills
2+
displayName: "Hosted Toolbox MCP Skills Agent"
3+
4+
description: >
5+
A hosted agent that discovers MCP-based skills from a Foundry Toolbox
6+
and makes them available to the agent via the agent skills provider.
7+
8+
metadata:
9+
tags:
10+
- AI Agent Hosting
11+
- Azure AI AgentServer
12+
- Responses Protocol
13+
- Agent Framework
14+
- MCP
15+
- Model Context Protocol
16+
- Agent Skills
17+
- Foundry Toolbox
18+
- Foundry Toolbox Skills
19+
20+
template:
21+
name: hosted-toolbox-mcp-skills
22+
kind: hosted
23+
protocols:
24+
- protocol: responses
25+
version: 1.0.0
26+
resources:
27+
cpu: "0.25"
28+
memory: 0.5Gi
29+
environment_variables:
30+
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
31+
value: "{{AZURE_AI_MODEL_DEPLOYMENT_NAME}}"
32+
- name: TOOLBOX_NAME
33+
value: "{{TOOLBOX_NAME}}"
34+
parameters:
35+
properties:
36+
- name: TOOLBOX_NAME
37+
secret: false
38+
description: Name of the Foundry Toolbox to connect to for MCP skill discovery
39+
resources:
40+
- kind: model
41+
id: gpt-5
42+
name: AZURE_AI_MODEL_DEPLOYMENT_NAME
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml
2+
kind: hosted
3+
name: hosted-toolbox-mcp-skills
4+
protocols:
5+
- protocol: responses
6+
version: 1.0.0
7+
resources:
8+
cpu: "0.25"
9+
memory: 0.5Gi
10+
environment_variables:
11+
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
12+
value: ${AZURE_AI_MODEL_DEPLOYMENT_NAME}
13+
- name: TOOLBOX_NAME
14+
value: ${TOOLBOX_NAME}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
import asyncio
4+
import os
5+
from collections.abc import Callable, Generator
6+
7+
import httpx
8+
from agent_framework import Agent, MCPSkillsSource, SkillsProvider
9+
from agent_framework.foundry import FoundryChatClient
10+
from agent_framework_foundry_hosting import ResponsesHostServer
11+
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
12+
from dotenv import load_dotenv
13+
from mcp.client.session import ClientSession
14+
from mcp.client.streamable_http import streamable_http_client
15+
16+
# Load environment variables from .env file
17+
load_dotenv()
18+
19+
20+
class ToolboxAuth(httpx.Auth):
21+
"""Attach a fresh Foundry bearer token to every request."""
22+
23+
def __init__(self, token_provider: Callable[[], str]):
24+
self._get_token = token_provider
25+
26+
def auth_flow(self, request: httpx.Request) -> Generator[httpx.Request, httpx.Response, None]:
27+
request.headers["Authorization"] = f"Bearer {self._get_token()}"
28+
yield request
29+
30+
31+
async def main() -> None:
32+
project_endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
33+
deployment = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"]
34+
toolbox_name = os.environ["TOOLBOX_NAME"]
35+
36+
# Build the Toolbox MCP URL from the project endpoint and toolbox name.
37+
toolbox_mcp_url = f"{project_endpoint.rstrip('/')}/toolboxes/{toolbox_name}/mcp?api-version=v1"
38+
39+
credential = DefaultAzureCredential()
40+
41+
# Create a token provider for Foundry bearer auth
42+
token_provider = get_bearer_token_provider(credential, "https://ai.azure.com/.default")
43+
44+
# ── Connect to the Foundry Toolbox MCP endpoint ──────────────────────────
45+
# Create an HTTP client that attaches a fresh Foundry bearer token to every
46+
# request and advertises the toolbox preview feature flag.
47+
async with (
48+
httpx.AsyncClient(
49+
auth=ToolboxAuth(token_provider),
50+
headers={"Foundry-Features": "Toolboxes=V1Preview"},
51+
timeout=httpx.Timeout(30.0, read=300.0),
52+
follow_redirects=True,
53+
) as http_client,
54+
streamable_http_client(
55+
url=toolbox_mcp_url,
56+
http_client=http_client,
57+
) as (read, write, _),
58+
ClientSession(read, write) as session,
59+
):
60+
await session.initialize()
61+
62+
print(f"Connected to Foundry Toolbox '{toolbox_name}' MCP server.")
63+
64+
# ── Configure MCP-based skills provider ──────────────────────────────
65+
# MCPSkillsSource reads skill://index.json and creates one MCPSkill per
66+
# skill-md entry; SKILL.md bodies are fetched on demand via
67+
# resources/read.
68+
skills_provider = SkillsProvider(MCPSkillsSource(client=session))
69+
70+
# ── Create the agent ─────────────────────────────────────────────────
71+
client = FoundryChatClient(
72+
project_endpoint=project_endpoint,
73+
model=deployment,
74+
credential=credential,
75+
)
76+
77+
agent = Agent(
78+
client=client,
79+
name=os.environ.get("AGENT_NAME", "hosted-toolbox-mcp-skills"),
80+
instructions="You are a helpful assistant.",
81+
context_providers=[skills_provider],
82+
# History will be managed by the hosting infrastructure, thus there
83+
# is no need to store history by the service. Learn more at:
84+
# https://developers.openai.com/api/reference/resources/responses/methods/create
85+
default_options={"store": False},
86+
)
87+
88+
# ── Build and run the host ───────────────────────────────────────────
89+
server = ResponsesHostServer(agent)
90+
await server.run_async()
91+
92+
93+
if __name__ == "__main__":
94+
asyncio.run(main())
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
agent-framework
2+
agent-framework-foundry-hosting
3+
4+
mcp>=1.24.0,<2

0 commit comments

Comments
 (0)