Python: Add Hosted-ToolboxMcpSkills sample#6359
Conversation
Port of the .NET Hosted-ToolboxMcpSkills sample (PR microsoft#6175) to Python. Demonstrates hosting an agent that discovers MCP-based skills from a Foundry Toolbox MCP endpoint using SkillsProvider(MCPSkillsSource(...)) as a context provider, implementing the Agent Skills progressive- disclosure pattern (advertise, load, read resources). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Pull request overview
Adds a new Python hosted-agent sample that mirrors the .NET “Hosted-ToolboxMcpSkills” scenario: discovering MCP-served Agent Skills from a Foundry Toolbox and injecting them via SkillsProvider(MCPSkillsSource(...)) in a Responses-protocol host. Also improves the existing Foundry Toolbox sample manifest/docs and updates hosting to surface multiple consent links when the Foundry MCP gateway returns a structured consent error payload.
Changes:
- New sample
12_foundry_toolbox_mcp_skills(code + container + deployment descriptors + docs) showing toolbox skill discovery and progressive disclosure. - Updates
04_foundry_toolboxsample to provide a concrete, default PAT-based GitHub MCP connection (with OAuth2 alternative) and clearer README guidance. - Enhances Foundry hosting consent interception to parse structured gateway consent errors and emit one
OAuthConsentRequestOutputItemper tool source; updates unit tests accordingly.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| python/samples/04-hosting/foundry-hosted-agents/responses/12_foundry_toolbox_mcp_skills/requirements.txt | Sample dependencies for toolbox MCP skills discovery. |
| python/samples/04-hosting/foundry-hosted-agents/responses/12_foundry_toolbox_mcp_skills/README.md | End-to-end documentation for the new hosted sample and skill setup. |
| python/samples/04-hosting/foundry-hosted-agents/responses/12_foundry_toolbox_mcp_skills/main.py | Implements MCP connection + MCPSkillsSource → SkillsProvider injection + Responses host. |
| python/samples/04-hosting/foundry-hosted-agents/responses/12_foundry_toolbox_mcp_skills/Dockerfile | Containerizes the new sample for hosted deployment. |
| python/samples/04-hosting/foundry-hosted-agents/responses/12_foundry_toolbox_mcp_skills/agent.yaml | Container agent deployment descriptor for Foundry hosting. |
| python/samples/04-hosting/foundry-hosted-agents/responses/12_foundry_toolbox_mcp_skills/agent.manifest.yaml | azd ai agent init manifest template for the new sample. |
| python/samples/04-hosting/foundry-hosted-agents/responses/12_foundry_toolbox_mcp_skills/.env.example | Environment variable template for local runs. |
| python/samples/04-hosting/foundry-hosted-agents/responses/12_foundry_toolbox_mcp_skills/.dockerignore | Excludes local artifacts/secrets from Docker build context. |
| python/samples/04-hosting/foundry-hosted-agents/responses/04_foundry_toolbox/requirements.txt | Normalizes sample requirements (uncomments framework deps). |
| python/samples/04-hosting/foundry-hosted-agents/responses/04_foundry_toolbox/README.md | Clarifies default GitHub MCP auth (PAT) and how to switch to OAuth2. |
| python/samples/04-hosting/foundry-hosted-agents/responses/04_foundry_toolbox/agent.manifest.yaml | Makes toolbox/connection resources concrete and prompts for needed parameters. |
| python/samples/04-hosting/foundry-hosted-agents/README.md | Adds the new sample to the hosting samples index table. |
| python/packages/foundry_hosting/tests/test_responses.py | Updates consent parsing tests for structured gateway messages and multi-consent results. |
| python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py | Parses structured consent payloads and emits multiple consent request output items. |
| else: | ||
| logger.warning("Consent URL in error message is not a valid URL: %s", consent_url) # type: ignore |
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 79%
✓ Correctness
The PR refactors consent error handling from a single URL string to a structured JSON payload containing multiple consent errors. The implementation is correct: the JSON parsing is defensive (handles missing '{', malformed JSON, missing keys), the function guarantees a non-empty list when returning non-None (so the handler loop is safe), and the ConsentError dataclass has auto-generated eq making test assertions work correctly. The error code change from -32007 to -32006 is intentional per the protocol update. The sample code properly scopes the MCP session within the async-with block that encloses server.run_async(). No correctness issues found.
✓ Security Reliability
The PR refactors consent error handling from a single URL string to a structured JSON payload with multiple consent errors, adds a new MCP skills sample, and un-comments toolbox configuration in an existing sample. The JSON parsing in consent_url_from_error is well-guarded with try/except and type checks, and degrades gracefully on malformed input. No critical security or reliability issues found. One defense-in-depth suggestion regarding URL scheme validation on extracted consent URLs.
✓ Test Coverage
The test changes correctly update existing tests to match the new
consent_url_from_errorreturn type (list[ConsentError] | None) and add a test for the no-JSON-in-message case. However, the new multi-consent-error iteration path in_handle_inner_agent(the for-loop emitting multiple OAuth items) has no integration test exercising it with >1 error. Additionally, thejson.JSONDecodeErrorcatch branch in the parser is unreached by any test.
✓ Design Approach
I found one design-level gap in the new hosted MCP-skills sample: unlike the existing toolbox-backed hosted samples, it only supports constructing the MCP URL from project endpoint + toolbox name, so it drops the explicit-endpoint input path that this repo already uses and documents for local development. I did not find a verified blocking issue in the consent-error changes from the files inspected.
Automated review by SergeyMenshykh's agents
| ): | ||
| consent_url = error["error"]["message"] # type: ignore | ||
| if isinstance(consent_url, str): | ||
| consent_errors.append(ConsentError(name=error.get("name", "Unknown"), consent_url=consent_url)) # type: ignore |
There was a problem hiding this comment.
Defense-in-depth: consent_url is extracted from a JSON payload inside an error message and ultimately presented to the user as a clickable link. Consider validating it starts with https:// before accepting it, to guard against non-HTTP schemes reaching the client if the gateway format changes or an intermediary is compromised.
| consent_errors.append(ConsentError(name=error.get("name", "Unknown"), consent_url=consent_url)) # type: ignore | |
| if isinstance(consent_url, str) and consent_url.startswith("https://"): | |
| consent_errors.append(ConsentError(name=error.get("name", "Unknown"), consent_url=consent_url)) # type: ignore |
There was a problem hiding this comment.
This code is not part of this PR — it came in from main during rebase. The comment should be addressed in a separate PR by the team that owns _responses.py.
| async def main() -> None: | ||
| project_endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] | ||
| deployment = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "gpt-5") | ||
| toolbox_name = os.environ["FOUNDRY_TOOLBOX_NAME"] |
There was a problem hiding this comment.
This hard-codes toolbox resolution to {FOUNDRY_PROJECT_ENDPOINT}/toolboxes/{FOUNDRY_TOOLBOX_NAME}/mcp?api-version=v1. The existing toolbox samples (04_foundry_toolbox, 06_files) prefer an explicit FOUNDRY_TOOLBOX_ENDPOINT env var and only fall back to project/name derivation, and the README documents that direct-endpoint path as the recommended local-dev flow. Consider adopting the same pattern so users with an existing MCP endpoint can point the sample at it directly.
Summary
Port of the .NET Hosted-ToolboxMcpSkills sample to Python.
Adds a new hosted agent sample at
python/samples/04-hosting/foundry-hosted-agents/responses/12_foundry_toolbox_mcp_skills/that demonstrates discovering MCP-based skills from a Foundry Toolbox and injecting them as a context provider usingSkillsProvider(MCPSkillsSource(...)).What this sample does
Connects to a Foundry Toolbox MCP endpoint and reads the well-known
skill://index.jsondiscovery resource to build aSkillsProvider. The provider implements the Agent Skills progressive-disclosure pattern:SKILL.mdbody is fetched on demand when the agent decides a skill is relevantAlso updates the parent
foundry-hosted-agents/README.mdtable to include the new sample.Partially solves: #6347