9 Commits

Author SHA1 Message Date
Giles Odigwe 25ec4c3b5c Python: Support archive-type MCP skills (source, toolbox, sample) (#7121)
* Python: Support archive-type MCP skills in MCPSkillsSource

Add `archive`-type skill support to `MCPSkillsSource` so an MCP server can
advertise packaged skills (ZIP / TAR / gzip-compressed TAR) that are
downloaded, safely unpacked to a local directory, and served like file-based
skills, while keeping the guarantee that MCP-delivered scripts are never
executed.

- Dispatch `skill://index.json` entries by `type`: `skill-md` (existing,
  fetched on demand) and `archive` (new). Unknown types are skipped.
- `_ArchiveEntryLoader` downloads, extracts, and prunes archive skills and
  delegates discovery to an internal `FileSkillsSource` created with no
  script extensions and no runner, so bundled scripts surface as read-only
  resources only.
- Hardened stdlib extraction: path-traversal (zip-slip) guard, non-regular
  TAR member skipping, and file-count / uncompressed-size / download-size
  limits.
- Configure via `archive_*` constructor kwargs (no options object, per Python
  conventions); use `CachingSkillsSource` for refresh rather than a source
  level refresh interval.
- Fix `FileSkillsSource` to treat `None` extensions as "use defaults" and an
  empty tuple as "discover none" (an empty tuple previously fell back to
  defaults).

Port of .NET PR #6631.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e358a4e-538f-46be-8c58-128b6182352d

* Propagate non-not-found archive download errors in MCPSkillsSource

Only swallow "resource not found" MCP errors when downloading an archive
resource; re-raise every other error (auth failure, INTERNAL_ERROR,
connection drop, timeout) so a transient transport failure is not silently
turned into a missing skill. This matches the existing failure model used by
`_try_read_index` and `MCPSkill.get_resource`, and avoids a failed
`CachingSkillsSource` refresh overwriting a previously cached list with a
partial result.

Add tests asserting archive-download INTERNAL_ERROR and ConnectionError
propagate out of `get_skills`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e358a4e-538f-46be-8c58-128b6182352d

* Python: Expose archive skill options on FoundryToolbox and demo in sample

- FoundryToolbox.as_skills_provider() now forwards the MCPSkillsSource archive
  options (archive_skills_directory, archive_resource_extensions,
  archive_resource_search_depth, archive_max_file_count, archive_max_size_bytes,
  archive_max_uncompressed_size_bytes). Only explicitly-set options are
  forwarded so unset ones keep the MCPSkillsSource defaults. This lets a hosted
  toolbox agent redirect archive extraction to a writable directory (the default
  is under the cwd, which may be read-only in a container).
- Add unit tests covering default (no options forwarded) and override forwarding.
- Update the 12_foundry_toolbox_mcp_skills sample to demonstrate all three
  progressive-disclosure stages with an archive skill: escalation-policy now
  ships a references/refund-matrix.md resource and is uploaded as a ZIP archive;
  main.py disables load_skill and read_skill_resource approval and points
  archive extraction at a temp directory. README, toolbox.yaml, and ignore files
  updated accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4f14f83d-1868-45c1-be1a-12f49a58ac36

* Python: Fix ty type error in toolbox archive-option test

Cast provider._source to _FoundryToolboxSkillsSource before accessing the
private _archive_options, so the ty checker (which runs over tests) resolves
the concrete type instead of the SkillsSource base. Replaces the mypy-style
type: ignore that ty did not honor.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4f14f83d-1868-45c1-be1a-12f49a58ac36

* Rework archive-type skill support in MCPSkillsSource to unpack archives
entirely in memory instead of extracting them to a local directory, and
apply reviewer feedback.

* Python: Raise on archive member path-traversal (zip-slip)

Treat a `..` path-traversal member in an archive skill as a hostile archive
and reject the whole skill, matching how the file-count and uncompressed-size
limits reject a malformed archive (previously the member was silently skipped
while the rest of the skill still loaded).

- `_normalize_archive_member_name` now raises `ValueError` on a `..` escape;
  benign degenerate entries (empty, `.`, `/`) still return None (skipped) and
  absolute paths are still neutralized to relative. The raise propagates to
  `_ArchiveEntryLoader._build_skill`, which already skips the skill on error.
- Update tests: traversal cases now assert a raise, and add an end-to-end test
  that a zip-slip archive drops the whole skill.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e358a4e-538f-46be-8c58-128b6182352d

* Python: Revert archive skill demo in toolbox MCP skills sample

Restore the 12_foundry_toolbox_mcp_skills sample to its pre-PR, skill-md-only
form (matching the .NET Agent_Step26_FoundryToolboxMcpSkills sample, which uses
skill-md and no ZIP archive):

- Revert main.py, toolbox.yaml, README.md, .azdignore, .dockerignore, and
  escalation-policy/SKILL.md to the single-file SKILL.md version.
- Remove the archive demo files added by this PR (.gitignore and
  escalation-policy/references/refund-matrix.md).
- Soften two README notes so they no longer claim archive skills are
  unsupported/silently dropped (this PR adds archive support); instead frame
  single-file SKILL.md as a focus choice and point to the archive_* options.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e358a4e-538f-46be-8c58-128b6182352d

* Python: Clarify archive framing in mcp_based_skill sample README

The mcp_based_skill sample is a generic MCP consumer that discovers whatever
the server advertises; it does not itself demonstrate archive skills. Reword
the archive note so it reads as an MCPSkillsSource capability rather than a
sample feature, and fix the stale "unpacked to a local directory" claim to
"unpacked in memory" (matching the in-memory extraction implementation).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e358a4e-538f-46be-8c58-128b6182352d

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e358a4e-538f-46be-8c58-128b6182352d
Copilot-Session: 4f14f83d-1868-45c1-be1a-12f49a58ac36
2026-07-30 20:57:51 +00:00
Eduard van Valkenburg b64a2e2f82 Python: add feature-usage User-Agent telemetry (#7420)
* Python: add first-pass feature usage telemetry

Add the 128-bit feature accumulator, package-local indexes, activation markers, and destination-scoped User-Agent emission for the initial Python implementation slice.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* Python: track declarative feature usage

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* Python: complete feature usage telemetry

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* Python: report core version in User-Agent

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* Python: configure Lab telemetry import path

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* Python: preserve telemetry transport behavior

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* Python: preserve caller-owned Foundry transports

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

* Python: remove stale Anthropic test import

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f

---------

Copilot-Session: 346bf168-b668-4c4a-a8db-a67282ee5e5f
2026-07-30 10:24:34 +00:00
Chinedum Echeta 217912a2c0 Python: Support async credentials in FoundryToolbox (#7208)
* Python: Support async credentials in `FoundryToolbox`

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Refactor: Use AzureCredentialTypes for credential type annotations in Toolbox classes

* Remove auth_flow method from _ToolboxAuth class

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-23 17:57:31 +00:00
pratik wayase 0841116330 Python: fix(foundry_hosting): preserve auth credentials across FoundryToolbox reconnections (#7202)
* fix(foundry_hosting): preserve auth credentials across FoundryToolbox reconnections

* Address copilot comments

* fix syntax check

* Fix tests

* Fix formatting

* Fix formatting

---------

Co-authored-by: Tao Chen <taochen@microsoft.com>
2026-07-23 17:15:19 +00:00
Giles Odigwe 1466d68cf1 Python: make FoundryToolbox.as_skills_provider() disable_caching effective (#7135)
* Python: make FoundryToolbox.as_skills_provider() disable_caching effective

as_skills_provider() forwarded disable_caching to SkillsProvider, which
ignores it for a caller-supplied SkillsSource, so it was a no-op and the
toolbox re-read skill://index.json on every agent run.

Compose caching in as_skills_provider() instead: wrap the context-independent
_FoundryToolboxSkillsSource in DeduplicatingSkillsSource(CachingSkillsSource(...)).
Add a cache_refresh_interval param, fix the docstring, and add tests covering
cached, disabled, and refresh-interval behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84150ec4-6f7c-4ef8-b9fb-12fa11652773

* Clarify caller-invariant skill-set wording in as_skills_provider docs

Emphasize that the toolbox advertises the same skill set to every caller (the
per-request call-id governs execution/authorization, not which skills are
listed) rather than leaning on 'ignores SkillsSourceContext'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84150ec4-6f7c-4ef8-b9fb-12fa11652773

* Make MCP skills reconnect-safe via session_provider

Cached MCPSkill objects captured the MCP ClientSession at construction, so
after a FoundryToolbox reconnect (which replaces its session) load_skill and
read_skill_resource would fail against the closed session. This regressed once
as_skills_provider() started caching discovery by default.

Add an optional session_provider callable to MCPSkillsSource and MCPSkill
(exactly one of client or session_provider). When supplied, the session is
resolved on every fetch, mirroring how MCPTool resolves self.session live at
call time. _FoundryToolboxSkillsSource now passes a provider that returns the
toolbox's current session, so cached skills always use the live session.

The fixed client= path is unchanged and backward-compatible. Update core tests,
foundry_hosting tests, and core AGENTS.md.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84150ec4-6f7c-4ef8-b9fb-12fa11652773

* Fix ty error: type captured session_provider as Callable in test

ty could not call the provider narrowed from \object\ (Top callable). Type the
captured value as Callable[[], object] and drop the redundant callable() assert.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84150ec4-6f7c-4ef8-b9fb-12fa11652773

* Simplify _resolve_mcp_session_provider per review

Address review feedback: replace the dense (client is None) == (session_provider
is None) guard with explicit branches, and drop the cast by binding the narrowed
client to a typed local. Keeps strict 'exactly one' semantics (raises on both and
on neither), matching the codebase convention (e.g. security.py mcp_tool/url).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84150ec4-6f7c-4ef8-b9fb-12fa11652773

* Add PR #7135 entries to the 1.12.0 changelog

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84150ec4-6f7c-4ef8-b9fb-12fa11652773

* Drop redundant @pytest.mark.asyncio from MCP skills tests

asyncio_mode is 'auto', so the marker is unnecessary. Remove it from the whole
file for consistency with the async-by-default convention. Per review feedback.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84150ec4-6f7c-4ef8-b9fb-12fa11652773

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-21 18:02:38 +00:00
Giles Odigwe 56e9a8f74c Python: Make foundry toolbox MCP skills sample self-contained (#7099)
* Python: Make foundry toolbox MCP skills sample self-contained

Rework sample 12 (foundry_toolbox_mcp_skills) so users can build it from
zero with azd, mirroring samples 04 and 09:

- Bundle two single-file SKILL.md skills (support-style, escalation-policy)
  and a skills-only toolbox.yaml (with one connectionless code_interpreter
  tool, required by `azd ai toolbox create`).
- Rewrite the README as an azd-native, from-zero guide (create skills ->
  create toolbox -> set TOOLBOX_ENDPOINT -> run) and fix the stale
  MCPSkillsSource API description to match main.py.
- Switch config from TOOLBOX_NAME to the versioned TOOLBOX_ENDPOINT
  (.env.example, agent.yaml, agent.manifest.yaml); add .azdignore.

Also enable the sample to run unattended behind ResponsesHostServer:

- Forward disable_load_skill_approval / disable_read_skill_resource_approval
  / disable_run_skill_script_approval from FoundryToolbox.as_skills_provider()
  to the underlying SkillsProvider, so load_skill needs no approval round-trip
  (the Responses host runs without an AgentSession, which the default approval
  flow requires). main.py now uses as_skills_provider(disable_load_skill_approval=True).
- Add unit tests covering the default and overridden approval behaviour.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4f14f83d-1868-45c1-be1a-12f49a58ac36

* Python: Address PR review on toolbox MCP skills sample

- Remove the unused parameters section from agent.manifest.yaml (TOOLBOX_ENDPOINT
  is supplied via environment_variables, matching sample 04).
- README: state the sample is self-contained directly instead of contrasting
  with the C# sample.
- README: describe skill discovery behaviour without naming the internal
  MCPSkillsSource class.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4f14f83d-1868-45c1-be1a-12f49a58ac36

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-14 17:03:26 +00:00
Evan Mattson 12b029858e Build(deps): consolidate Dependabot dependency updates (#6984)
* Consolidate Dependabot dependency updates

* Restore method assignment suppression
2026-07-08 09:09:03 +00:00
Giles Odigwe 9c4cd07899 Python: Add SkillsSourceContext to SkillsSource.get_skills (#6895)
* Python: Add SkillsSourceContext to SkillsSource.get_skills

Thread an invocation context (agent + optional session) through the skill
source pipeline so sources and decorators can make context-aware decisions.

- Add frozen, experimental SkillsSourceContext(agent, session).
- Change SkillsSource.get_skills and all sources/decorators to accept and
  forward the context.
- Make FilteringSkillsSource predicate context-aware: (skill, context) -> bool.
- Add optional cache_isolation_key_selector to CachingSkillsSource for
  per-key cache isolation (None keeps the shared-bucket behavior).
- Build the context in SkillsProvider from before_run agent/session.
- Update foundry_hosting toolbox source, exports, tests, and docs.

Python port of .NET PR #6797.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Clarify skills source docstring examples

Address PR review: docstring examples referenced `context` without
constructing it. Add a `SkillsSourceContext` construction line (with a
placeholder agent) to each source example and a note that the provider
normally supplies it. Use `source_context` in the FilteringSkillsSource
example to avoid clashing with the predicate's `context` parameter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Fix CI type errors and skill_filtering sample predicate

Address CI failures from the SkillsSourceContext change:
- Update the skill_filtering sample to the 2-arg predicate signature
  (skill, context); the old 1-arg lambda would fail at runtime.
- Replace ad-hoc _StubAgent test stubs with the shared MockAgent /
  MockAgentSession from conftest so all type checkers (incl. ty) accept
  the SupportsAgentRun-typed agent. Add a small _NamedMockAgent subclass
  for tests needing distinct agent names, and drop now-unnecessary
  attr-defined ignores.
- Use cast(SupportsAgentRun, ...) in foundry_hosting tests, which have no
  shared mock infrastructure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Make SkillsProvider caching safe-by-default; clarify context docstrings

Address PR review comments:
- Do not auto-wrap a caller-supplied SkillsSource in the provider's default
  CachingSkillsSource. A shared, unkeyed cache around a context-aware source
  replays the first invocation's skills for later SkillsSourceContexts,
  leaking skills across agents/tenants. Default caching now applies only to
  the built-in, context-independent file/in-memory leaf sources
  (Deduplicating(Caching(leaf))), matching the .NET provider. Callers who
  want caching on a custom pipeline compose CachingSkillsSource (optionally
  with a cache_isolation_key_selector) themselves. disable_caching now only
  affects the built-in leaves. Adds a leak-prevention test.
- Reword the misleading "Unused by this source" context docstrings on the
  File/InMemory/MCP sources: the param is part of the get_skills contract;
  these sources just return the same skills regardless of context.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-07 09:41:45 +00:00
Tao Chen 7f3a2aec38 [BREAKING] Python: Foundry Hosted Agent V2 protocol upgrade (#6811)
* Upgrade to FHA protocol v2 + toolbox integration

* Scope checkpoints and approval storage by user id

* Add toolbox skills integration

* Fix formatting

* Add httpx lower and upper bound

* Update foundry-hosting package version

* Remove custom http client

* Revert "Remove custom http client"

This reverts commit 60f1d5aa52.

* Remove custom http client

* correct fail fast exceptino wording
2026-06-30 17:13:08 +00:00