Skip to content
Open
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
40 changes: 18 additions & 22 deletions notebooks/mastering-foundry-toolbox.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,6 @@
" starts with `if skip(...): ...`.\n",
"- `mcp_token()` - a bearer token scoped to `https://ai.azure.com/.default`, the audience the\n",
" toolbox MCP endpoint expects.\n",
"- `TOOLBOX_HEADERS` - **every** call to a toolbox MCP endpoint must carry\n",
" `Foundry-Features: Toolboxes=V1Preview`. Forgetting it is the #1 cause of 404s.\n",
"- `created_resources` - a tracker the cleanup section walks in reverse."
]
},
Expand Down Expand Up @@ -242,9 +240,6 @@
"# The MCP endpoint audience is ai.azure.com (NOT management.azure.com).\n",
"TOOLBOX_SCOPE = \"https://ai.azure.com/.default\"\n",
"\n",
"# Mandatory on every toolbox MCP request while the feature is in preview.\n",
"TOOLBOX_HEADERS = {\"Foundry-Features\": \"Toolboxes=V1Preview\"}\n",
"\n",
"credential = DefaultAzureCredential()\n",
"project = AIProjectClient(endpoint=PROJECT_ENDPOINT, credential=credential)\n",
"_token_provider = get_bearer_token_provider(credential, TOOLBOX_SCOPE)\n",
Expand Down Expand Up @@ -1310,6 +1305,10 @@
"reference it in the **separate `skills=` list** with `ToolboxSkillReference(name, version)`. Omit\n",
"`version` to track the skill's **default** version; pin it to freeze on an immutable version.\n",
"\n",
"> Skills CRUD (`project.beta.skills.*` create / update-default) is the **only** flow that needs a\n",
"> preview feature flag, and the `beta.skills` SDK sends it for you. Reading skills as MCP resources\n",
"> (section 10b) and regular toolbox/tool calls need **no** preview header.\n",
"\n",
"**Key parameters** (`ToolboxSkillReference`):\n",
"- `name` - **required** `str`; the published skill name.\n",
"- `version` - optional; `None` = default version, a value = pinned immutable version.\n",
Expand Down Expand Up @@ -1428,7 +1427,7 @@
"\n",
"The request body is exactly the JSON the typed classes serialize to, so you can keep a versioned\n",
"JSON manifest in source control. Every call needs the bearer token (scope\n",
"`https://ai.azure.com/.default`) **and** the `Foundry-Features: Toolboxes=V1Preview` header.\n",
"`https://ai.azure.com/.default`).\n",
"\n",
"> `azd` (the Foundry extension) handles **connections** and agent provisioning, but a **toolbox**\n",
"> is authored with the SDK above or this REST API - there is no `azd ai toolbox` command. Create\n",
Expand Down Expand Up @@ -1494,8 +1493,7 @@
" print(f\" POST {create_url}\")\n",
" print(f\" PATCH {patch_url} body={{'default_version': '<new>'}}\")\n",
"else:\n",
" headers = {\"Authorization\": f\"Bearer {mcp_token()}\", **TOOLBOX_HEADERS,\n",
" \"Content-Type\": \"application/json\"}\n",
" headers = {\"Authorization\": f\"Bearer {mcp_token()}\", \"Content-Type\": \"application/json\"}\n",
"\n",
" # 1) Create a new immutable version (auto-creates the toolbox on first call).\n",
" resp = httpx.post(create_url, headers=headers, json=version_body, timeout=60)\n",
Expand Down Expand Up @@ -1765,8 +1763,7 @@
"| **Developer** | `{project}/toolboxes/{name}/versions/{version}/mcp?api-version=v1` | one pinned version |\n",
"| **Consumer** | `{project}/toolboxes/{name}/mcp?api-version=v1` | the **default** version |\n",
"\n",
"Both require the bearer token (scope `https://ai.azure.com/.default`) **and** the\n",
"`Foundry-Features: Toolboxes=V1Preview` header on every request."
"Both require only the bearer token (scope `https://ai.azure.com/.default`) - no preview header."
]
},
{
Expand Down Expand Up @@ -1818,8 +1815,8 @@
"claim (tool count *and* input-schema payload size). Then we run a `tool_search` -> `call_tool`\n",
"round-trip and read each tool's `_meta.tool_configuration` (which carries `require_approval`).\n",
"\n",
"We use the `mcp` SDK's streamable-HTTP client, passing the bearer token and the mandatory\n",
"preview header. If a tool's connection uses `oauth2`, the first call returns `CONSENT_REQUIRED`\n",
"We use the `mcp` SDK's streamable-HTTP client, passing the bearer token (no preview\n",
"header is needed for tool calls). If a tool's connection uses `oauth2`, the first call returns `CONSENT_REQUIRED`\n",
"(`-32006`) with a consent URL - we surface it so you can consent and retry."
]
},
Expand All @@ -1844,7 +1841,7 @@
"\n",
"async def _list_tools(url: str):\n",
" \"\"\"tools/list against a specific toolbox MCP endpoint.\"\"\"\n",
" headers = {**TOOLBOX_HEADERS, \"Authorization\": f\"Bearer {mcp_token()}\"}\n",
" headers = {\"Authorization\": f\"Bearer {mcp_token()}\"}\n",
" async with streamablehttp_client(url, headers=headers) as (read, write, _):\n",
" async with ClientSession(read, write) as session:\n",
" await session.initialize()\n",
Expand All @@ -1869,7 +1866,7 @@
" assert \"tool_search\" in after_names, \"Tool Search not active - is the default version search-first?\"\n",
"\n",
" # Drive the meta-tool, then read approval config off a listed tool.\n",
" headers = {**TOOLBOX_HEADERS, \"Authorization\": f\"Bearer {mcp_token()}\"}\n",
" headers = {\"Authorization\": f\"Bearer {mcp_token()}\"}\n",
" async with streamablehttp_client(CONSUMER_URL, headers=headers) as (read, write, _):\n",
" async with ClientSession(read, write) as session:\n",
" await session.initialize()\n",
Expand Down Expand Up @@ -1921,7 +1918,7 @@
"# Skills attach to a toolbox as MCP *resources* (SEP-2640), not tools. The same streamable-HTTP\n",
"# MCP session that lists tools also lists and reads skills - no Foundry SDK needed.\n",
"async def consume_skills():\n",
" headers = {**TOOLBOX_HEADERS, \"Authorization\": f\"Bearer {mcp_token()}\"}\n",
" headers = {\"Authorization\": f\"Bearer {mcp_token()}\"}\n",
" async with streamablehttp_client(CONSUMER_URL, headers=headers) as (read, write, _):\n",
" async with ClientSession(read, write) as session:\n",
" await session.initialize()\n",
Expand Down Expand Up @@ -1958,7 +1955,7 @@
"\n",
"The whole point of one governed endpoint is that *any* MCP client can use it unchanged. Here are\n",
"three: **Microsoft Agent Framework**, **LangGraph**, and the **Copilot SDK**. Each just needs the\n",
"consumer URL, a bearer token, and the preview header."
"consumer URL and a bearer token."
]
},
{
Expand All @@ -1985,8 +1982,8 @@
"source": [
"# --- Microsoft Agent Framework -------------------------------------------------\n",
"# MAF speaks MCP natively via MCPStreamableHTTPTool. Point it at the consumer URL.\n",
"# Auth note: the toolbox MCP endpoint needs a bearer token + the preview header on\n",
"# EVERY request, including the initialize handshake. MAF's header_provider only injects\n",
"# Auth note: the toolbox MCP endpoint needs a bearer token on every request, including the\n",
"# initialize handshake. MAF's header_provider only injects\n",
"# on tool *calls*, so we hand it a pre-authenticated http_client whose default headers\n",
"# cover connect + initialize. load_prompts=False skips a prompts/list the endpoint\n",
"# doesn't serve, and we build FoundryChatClient from the endpoint + credential (the\n",
Expand All @@ -1997,7 +1994,7 @@
"\n",
"async def run_maf():\n",
" http = httpx.AsyncClient(\n",
" headers={**TOOLBOX_HEADERS, \"Authorization\": f\"Bearer {mcp_token()}\"},\n",
" headers={\"Authorization\": f\"Bearer {mcp_token()}\"},\n",
" follow_redirects=True,\n",
" timeout=httpx.Timeout(30.0, read=300.0),\n",
" )\n",
Expand Down Expand Up @@ -2105,7 +2102,7 @@
"source": [
"# --- Copilot SDK ---------------------------------------------------------------\n",
"# The GitHub Copilot SDK (pip install github-copilot-sdk; Python 3.11+) consumes the toolbox as a\n",
"# remote MCP server - same consumer URL, same bearer token + preview header. Remote MCP servers are\n",
"# remote MCP server - same consumer URL, same bearer token. Remote MCP servers are\n",
"# configured through create_session(..., mcp_servers={...}) with type \"http\".\n",
"# Docs: https://docs.github.com/copilot/how-tos/copilot-sdk/features/mcp\n",
"if not os.getenv(\"PROJECT_ENDPOINT\"):\n",
Expand All @@ -2127,7 +2124,7 @@
" \"foundry_toolbox\": {\n",
" \"type\": \"http\",\n",
" \"url\": CONSUMER_URL,\n",
" \"headers\": {**TOOLBOX_HEADERS, \"Authorization\": f\"Bearer {mcp_token()}\"},\n",
" \"headers\": {\"Authorization\": f\"Bearer {mcp_token()}\"},\n",
" \"tools\": [\"*\"], # expose every toolbox tool (tool_search included)\n",
" },\n",
" },\n",
Expand Down Expand Up @@ -2188,7 +2185,6 @@
"# toolbox_tool = MCPStreamableHTTPTool(\n",
"# name=\"foundry_toolbox\",\n",
"# url=CONSUMER_URL, # same consumer URL as local\n",
"# headers=TOOLBOX_HEADERS, # token injected by the runtime\n",
"# )\n",
"# return ChatAgent(\n",
"# chat_client=AzureAIAgentClient(project_client=project, model=MODEL_DEPLOYMENT),\n",
Expand Down
Loading