From 14a884b9214fce2105b82eb5cebdf72863e25b39 Mon Sep 17 00:00:00 2001 From: Linda Li Date: Thu, 16 Jul 2026 00:59:09 -0700 Subject: [PATCH] Remove toolbox preview header from notebook; only skills CRUD needs a preview flag --- notebooks/mastering-foundry-toolbox.ipynb | 40 ++++++++++------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/notebooks/mastering-foundry-toolbox.ipynb b/notebooks/mastering-foundry-toolbox.ipynb index 8bbc1ad..61c81dd 100644 --- a/notebooks/mastering-foundry-toolbox.ipynb +++ b/notebooks/mastering-foundry-toolbox.ipynb @@ -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." ] }, @@ -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", @@ -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", @@ -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", @@ -1494,8 +1493,7 @@ " print(f\" POST {create_url}\")\n", " print(f\" PATCH {patch_url} body={{'default_version': ''}}\")\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", @@ -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." ] }, { @@ -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." ] }, @@ -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", @@ -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", @@ -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", @@ -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." ] }, { @@ -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", @@ -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", @@ -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", @@ -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", @@ -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",