-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Python: Add Foundry Toolbox MCP skills hosted agent sample #6363
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
SergeyMenshykh
wants to merge
5
commits into
microsoft:main
Choose a base branch
from
SergeyMenshykh:sergeymenshykh/supreme-fiesta
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
ae911f5
Add 12_foundry_toolbox_mcp_skills hosted agent sample
semenshi 39868fd
Fix env var reference in README and reuse local var in main.py
semenshi 7406db3
Potential fix for pull request finding
SergeyMenshykh 7d7db48
Require AZURE_AI_MODEL_DEPLOYMENT_NAME and use placeholder in .env.ex…
semenshi 371385e
Merge commit '7406db30f30eb7aca83606dc1af4ed01a72f7d2c' into sergeyme…
semenshi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
...es/04-hosting/foundry-hosted-agents/responses/12_foundry_toolbox_mcp_skills/.dockerignore
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| .venv | ||
| __pycache__ | ||
| *.pyc | ||
| *.pyo | ||
| *.pyd | ||
| .Python | ||
| .env |
3 changes: 3 additions & 0 deletions
3
...les/04-hosting/foundry-hosted-agents/responses/12_foundry_toolbox_mcp_skills/.env.example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| FOUNDRY_PROJECT_ENDPOINT="..." | ||
| AZURE_AI_MODEL_DEPLOYMENT_NAME="..." | ||
| TOOLBOX_NAME="..." | ||
18 changes: 18 additions & 0 deletions
18
...mples/04-hosting/foundry-hosted-agents/responses/12_foundry_toolbox_mcp_skills/Dockerfile
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"] |
72 changes: 72 additions & 0 deletions
72
...hosting/foundry-hosted-agents/responses/12_foundry_toolbox_mcp_skills/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. |
42 changes: 42 additions & 0 deletions
42
...hosting/foundry-hosted-agents/responses/12_foundry_toolbox_mcp_skills/agent.manifest.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
14 changes: 14 additions & 0 deletions
14
...mples/04-hosting/foundry-hosted-agents/responses/12_foundry_toolbox_mcp_skills/agent.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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} |
94 changes: 94 additions & 0 deletions
94
.../samples/04-hosting/foundry-hosted-agents/responses/12_foundry_toolbox_mcp_skills/main.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" | ||
|
|
||
|
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 | ||
|
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()) | ||
4 changes: 4 additions & 0 deletions
4
...04-hosting/foundry-hosted-agents/responses/12_foundry_toolbox_mcp_skills/requirements.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.