Skip to content

Commit bcd2800

Browse files
giles17Copilot
andauthored
Python: Allow disabling approval for SkillsProvider tools (#6867)
* Python: Allow disabling approval for SkillsProvider tools Add disable_load_skill_approval, disable_read_skill_resource_approval, and disable_run_skill_script_approval keyword arguments to SkillsProvider.__init__ and SkillsProvider.from_paths. When set, the corresponding tool is registered with approval_mode=never_require so it runs without approval for trusted-skill scenarios. Approval remains required by default. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Preserve from_paths compatibility for SkillsProvider subclasses Forward the disable_*_approval kwargs from SkillsProvider.from_paths only when explicitly enabled, so subclasses that override __init__ with the previous signature keep working when the flags are left at their defaults. Add a regression test covering a legacy-signature subclass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 38b5f70 commit bcd2800

3 files changed

Lines changed: 208 additions & 15 deletions

File tree

python/packages/core/AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ agent_framework/
7676
- **`SkillScript`** - An executable script attached to a skill; holds either an inline `function` (code-defined, runs in-process) or a `path` to a file on disk (file-based, delegated to a runner). Exactly one must be provided.
7777
- **`SkillScriptRunner`** - Protocol for file-based script execution. Any callable matching `(skill, script, args) -> Any` satisfies it. Code-defined scripts do not use a runner.
7878
- **`SkillScriptArgumentParser`** - Public type alias for an optional callable `(raw args: dict | list[str] | str | None) -> dict | None` that converts the raw `args` value before an `InlineSkillScript` runs (applied before the inline list-args guard). It is an opt-in customization hook (port of .NET PR #6498) that lets callers support backends sending tool-call arguments in a non-conforming shape (e.g. vLLM JSON strings). The output is constrained to a `dict` (named keyword arguments) or `None`, because inline scripts bind arguments by keyword name. Supply it via the `argument_parser=` constructor arg on `InlineSkillScript`, `InlineSkill` (default for scripts added via `@skill.script`), or `ClassSkill` (default for scripts discovered via `@ClassSkill.script`). When `None` (the default), the raw value is used unchanged. File-based scripts are unaffected (their runner owns arg handling).
79-
- **`SkillsProvider`** - Context provider (extends `ContextProvider`) that discovers file-based skills from `SKILL.md` files and/or accepts code-defined `Skill` instances. Follows progressive disclosure: advertise → load → read resources / run scripts. All three tools it exposes (`load_skill`, `read_skill_resource`, `run_skill_script`) are registered with `approval_mode="always_require"`, so every skill operation needs approval. To run unattended, pass one of the static auto-approval rules to `ToolApprovalMiddleware` (via `auto_approval_rules`): `SkillsProvider.read_only_tools_auto_approval_rule` approves only the read-only tools (`load_skill`, `read_skill_resource`) while still prompting for `run_skill_script`, and `SkillsProvider.all_tools_auto_approval_rule` approves every skill tool including script execution. Both rules reject any call carrying a `server_label` so they stay scoped to this provider's local tools and never auto-approve a same-named hosted tool. The tool names are also exposed as class constants (`LOAD_SKILL_TOOL_NAME`, `READ_SKILL_RESOURCE_TOOL_NAME`, `RUN_SKILL_SCRIPT_TOOL_NAME`).
79+
- **`SkillsProvider`** - Context provider (extends `ContextProvider`) that discovers file-based skills from `SKILL.md` files and/or accepts code-defined `Skill` instances. Follows progressive disclosure: advertise → load → read resources / run scripts. By default all three tools it exposes (`load_skill`, `read_skill_resource`, `run_skill_script`) are registered with `approval_mode="always_require"`, so every skill operation needs approval. To run unattended, pass one of the static auto-approval rules to `ToolApprovalMiddleware` (via `auto_approval_rules`): `SkillsProvider.read_only_tools_auto_approval_rule` approves only the read-only tools (`load_skill`, `read_skill_resource`) while still prompting for `run_skill_script`, and `SkillsProvider.all_tools_auto_approval_rule` approves every skill tool including script execution. Both rules reject any call carrying a `server_label` so they stay scoped to this provider's local tools and never auto-approve a same-named hosted tool. Alternatively, for trusted skills, the constructor / `from_paths` kwargs `disable_load_skill_approval`, `disable_read_skill_resource_approval`, and `disable_run_skill_script_approval` (all default `False`) opt individual tools out of approval entirely by registering them with `approval_mode="never_require"` (the auto-approval rules only apply to tools that still require approval). The tool names are also exposed as class constants (`LOAD_SKILL_TOOL_NAME`, `READ_SKILL_RESOURCE_TOOL_NAME`, `RUN_SKILL_SCRIPT_TOOL_NAME`).
8080
- **`SkillsSource` decorators** - Skill sources are composable: `SkillsSource` is the abstract base, with concrete sources (`InMemorySkillsSource`, `FileSkillsSource`, `MCPSkillsSource`) and decorators that wrap an inner source — `AggregatingSkillsSource` (concatenate several sources), `FilteringSkillsSource` (predicate filter), `DeduplicatingSkillsSource` (first-wins by name), and `CachingSkillsSource` (cache the inner source's skills list). `DelegatingSkillsSource` is the abstract base for decorators. **Caching lives in the source pipeline, not the provider**: `SkillsProvider` wraps its resolved source in a `CachingSkillsSource` by default (so expensive filesystem/network discovery runs once and is reused), and rebuilds instructions/tools from the cached skills each run. Pass `disable_caching=True` to `SkillsProvider(...)` / `SkillsProvider.from_paths(...)` to skip the wrapping and re-query the source on every run. `CachingSkillsSource` shares a single in-flight fetch across concurrent callers and resets its cache on failure so the next call retries.
8181

8282
### Model Context Protocol (`_mcp.py`)

python/packages/core/agent_framework/_skills.py

Lines changed: 94 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959

6060
from ._feature_stage import ExperimentalFeature, experimental
6161
from ._sessions import ContextProvider
62-
from ._tools import FunctionTool
62+
from ._tools import ApprovalMode, FunctionTool
6363

6464
if TYPE_CHECKING:
6565
from mcp.client.session import ClientSession
@@ -1850,7 +1850,7 @@ class SkillsProvider(ContextProvider):
18501850
and file-based resource reads are guarded against path traversal and
18511851
symlink escape. Only use skills from trusted sources.
18521852
1853-
**Tool approval:** every tool exposed by this provider
1853+
**Tool approval:** by default every tool exposed by this provider
18541854
(``load_skill``, ``read_skill_resource``, and ``run_skill_script``) is
18551855
registered with ``approval_mode="always_require"``, so each skill operation
18561856
needs approval. To run unattended, pass one of the static
@@ -1859,7 +1859,12 @@ class SkillsProvider(ContextProvider):
18591859
:meth:`read_only_tools_auto_approval_rule` approves only the read-only tools
18601860
(``load_skill`` and ``read_skill_resource``) while still prompting for
18611861
``run_skill_script``, and :meth:`all_tools_auto_approval_rule` approves every
1862-
skill tool including script execution.
1862+
skill tool including script execution. Alternatively, for trusted skills,
1863+
set ``disable_load_skill_approval``, ``disable_read_skill_resource_approval``,
1864+
and/or ``disable_run_skill_script_approval`` to opt individual tools out of
1865+
approval entirely (those tools are registered with
1866+
``approval_mode="never_require"`` and are not surfaced as approval requests;
1867+
the auto-approval rules only apply to tools that still require approval).
18631868
18641869
Examples:
18651870
File-based factory (recommended for single-source file skills):
@@ -1949,7 +1954,10 @@ def _is_local_tool_call(function_call: Content) -> bool:
19491954
def read_only_tools_auto_approval_rule(function_call: Content) -> bool:
19501955
"""Auto-approval rule that approves only the read-only skill tools.
19511956
1952-
The tools exposed by :class:`SkillsProvider` always require approval.
1957+
By default the tools exposed by :class:`SkillsProvider` require
1958+
approval. This rule only applies to tools that still require approval;
1959+
tools opted out via the ``disable_*_approval`` constructor arguments run
1960+
without approval regardless.
19531961
Pass this rule to :class:`~agent_framework.ToolApprovalMiddleware` (via
19541962
``auto_approval_rules``) to automatically approve the tools that read
19551963
skill content (``load_skill`` and ``read_skill_resource``), while still
@@ -1975,7 +1983,10 @@ def read_only_tools_auto_approval_rule(function_call: Content) -> bool:
19751983
def all_tools_auto_approval_rule(function_call: Content) -> bool:
19761984
"""Auto-approval rule that approves every skill tool.
19771985
1978-
The tools exposed by :class:`SkillsProvider` always require approval.
1986+
By default the tools exposed by :class:`SkillsProvider` require
1987+
approval. This rule only applies to tools that still require approval;
1988+
tools opted out via the ``disable_*_approval`` constructor arguments run
1989+
without approval regardless.
19791990
Pass this rule to :class:`~agent_framework.ToolApprovalMiddleware` (via
19801991
``auto_approval_rules``) to automatically approve every skill tool,
19811992
including the script execution tool (``run_skill_script``).
@@ -2001,6 +2012,9 @@ def __init__(
20012012
*,
20022013
instruction_template: str | None = None,
20032014
disable_caching: bool = False,
2015+
disable_load_skill_approval: bool = False,
2016+
disable_read_skill_resource_approval: bool = False,
2017+
disable_run_skill_script_approval: bool = False,
20042018
source_id: str | None = None,
20052019
) -> None:
20062020
"""Initialize a SkillsProvider.
@@ -2029,14 +2043,33 @@ def __init__(
20292043
disable_caching: When ``True``, rebuilds tools and instructions
20302044
from the source on every invocation instead of caching
20312045
after the first build. Defaults to ``False``.
2046+
disable_load_skill_approval: When ``True``, the ``load_skill`` tool
2047+
is registered with ``approval_mode="never_require"`` so it runs
2048+
without approval. Defaults to ``False`` (approval required).
2049+
Only enable this for skills from a trusted source.
2050+
disable_read_skill_resource_approval: When ``True``, the
2051+
``read_skill_resource`` tool is registered with
2052+
``approval_mode="never_require"`` so it runs without approval.
2053+
Defaults to ``False`` (approval required). Only enable this for
2054+
skills from a trusted source.
2055+
disable_run_skill_script_approval: When ``True``, the
2056+
``run_skill_script`` tool is registered with
2057+
``approval_mode="never_require"`` so it runs without approval.
2058+
Defaults to ``False`` (approval required). Only enable this for
2059+
skills and scripts from a trusted source.
20322060
source_id: Unique identifier for this provider instance.
20332061
20342062
.. note::
20352063
2036-
All skill tools require approval. To approve them
2064+
By default every skill tool requires approval. To approve them
20372065
automatically, pass :meth:`read_only_tools_auto_approval_rule` or
20382066
:meth:`all_tools_auto_approval_rule` to
2039-
:class:`~agent_framework.ToolApprovalMiddleware`. See
2067+
:class:`~agent_framework.ToolApprovalMiddleware`. Alternatively, for
2068+
trusted skills, set one or more of
2069+
``disable_load_skill_approval``, ``disable_read_skill_resource_approval``,
2070+
and ``disable_run_skill_script_approval`` to opt individual tools out
2071+
of approval entirely (the auto-approval rules only apply to tools
2072+
that still require approval). See
20402073
``samples/02-agents/skills/skills_auto_approval/skills_auto_approval.py``
20412074
for the auto-approval pattern and
20422075
``samples/02-agents/skills/script_approval/script_approval.py`` for
@@ -2067,6 +2100,9 @@ def __init__(
20672100
self._source = source
20682101
self._instruction_template = instruction_template
20692102
self._disable_caching = disable_caching
2103+
self._disable_load_skill_approval = disable_load_skill_approval
2104+
self._disable_read_skill_resource_approval = disable_read_skill_resource_approval
2105+
self._disable_run_skill_script_approval = disable_run_skill_script_approval
20702106

20712107
@classmethod
20722108
def from_paths(
@@ -2081,6 +2117,9 @@ def from_paths(
20812117
resource_filter: Callable[[str, str], bool] | None = None,
20822118
instruction_template: str | None = None,
20832119
disable_caching: bool = False,
2120+
disable_load_skill_approval: bool = False,
2121+
disable_read_skill_resource_approval: bool = False,
2122+
disable_run_skill_script_approval: bool = False,
20842123
source_id: str | None = None,
20852124
) -> _TSkillsProvider:
20862125
"""Create a provider from one or more file-based skill directories.
@@ -2118,17 +2157,31 @@ def from_paths(
21182157
disable_caching: When ``True``, rebuilds tools and instructions
21192158
from the source on every invocation instead of caching
21202159
after the first build.
2160+
disable_load_skill_approval: When ``True``, the ``load_skill`` tool
2161+
runs without approval. Defaults to ``False``. Only enable this
2162+
for skills from a trusted source.
2163+
disable_read_skill_resource_approval: When ``True``, the
2164+
``read_skill_resource`` tool runs without approval. Defaults to
2165+
``False``. Only enable this for skills from a trusted source.
2166+
disable_run_skill_script_approval: When ``True``, the
2167+
``run_skill_script`` tool runs without approval. Defaults to
2168+
``False``. Only enable this for skills and scripts from a
2169+
trusted source.
21212170
source_id: Unique identifier for this provider instance.
21222171
21232172
Returns:
21242173
A configured :class:`SkillsProvider`.
21252174
21262175
.. note::
21272176
2128-
All skill tools require approval. To approve them
2177+
By default every skill tool requires approval. To approve them
21292178
automatically, pass :meth:`read_only_tools_auto_approval_rule` or
21302179
:meth:`all_tools_auto_approval_rule` to
2131-
:class:`~agent_framework.ToolApprovalMiddleware`.
2180+
:class:`~agent_framework.ToolApprovalMiddleware`. Alternatively, for
2181+
trusted skills, set one or more of ``disable_load_skill_approval``,
2182+
``disable_read_skill_resource_approval``, and
2183+
``disable_run_skill_script_approval`` to opt individual tools out of
2184+
approval entirely.
21322185
"""
21332186
source = DeduplicatingSkillsSource(
21342187
FileSkillsSource(
@@ -2141,11 +2194,20 @@ def from_paths(
21412194
resource_filter=resource_filter,
21422195
)
21432196
)
2197+
# Only forward the approval-disable kwargs when explicitly enabled.
2198+
approval_overrides: dict[str, bool] = {}
2199+
if disable_load_skill_approval:
2200+
approval_overrides["disable_load_skill_approval"] = True
2201+
if disable_read_skill_resource_approval:
2202+
approval_overrides["disable_read_skill_resource_approval"] = True
2203+
if disable_run_skill_script_approval:
2204+
approval_overrides["disable_run_skill_script_approval"] = True
21442205
return cls(
21452206
source,
21462207
instruction_template=instruction_template,
21472208
disable_caching=disable_caching,
21482209
source_id=source_id,
2210+
**approval_overrides,
21492211
)
21502212

21512213
@staticmethod
@@ -2278,19 +2340,37 @@ async def before_run(
22782340
context.extend_instructions(self.source_id, instructions) # type: ignore[arg-type]
22792341
context.extend_tools(self.source_id, tools)
22802342

2343+
@staticmethod
2344+
def _approval_mode(approval_disabled: bool) -> ApprovalMode:
2345+
"""Return the ``approval_mode`` for a tool given its disable flag.
2346+
2347+
Args:
2348+
approval_disabled: When ``True``, the tool runs without approval.
2349+
2350+
Returns:
2351+
``"never_require"`` when approval is disabled, otherwise
2352+
``"always_require"``.
2353+
"""
2354+
return "never_require" if approval_disabled else "always_require"
2355+
22812356
def _create_tools(
22822357
self,
22832358
skills: Sequence[Skill],
22842359
) -> list[FunctionTool]:
22852360
"""Create the tool definitions for skill interaction.
22862361
22872362
Always includes ``load_skill``, ``read_skill_resource``, and
2288-
``run_skill_script``. Every tool is registered with
2363+
``run_skill_script``. By default every tool is registered with
22892364
``approval_mode="always_require"`` so each skill operation needs
22902365
approval; use :meth:`read_only_tools_auto_approval_rule` or
22912366
:meth:`all_tools_auto_approval_rule` with
22922367
:class:`~agent_framework.ToolApprovalMiddleware` to approve them
2293-
automatically.
2368+
automatically. For trusted skills, individual tools can be opted out of
2369+
approval entirely via the ``disable_load_skill_approval``,
2370+
``disable_read_skill_resource_approval``, and
2371+
``disable_run_skill_script_approval`` constructor arguments, in which
2372+
case the corresponding tool is registered with
2373+
``approval_mode="never_require"``.
22942374
22952375
Args:
22962376
skills: The skills to bind to tool handlers.
@@ -2315,7 +2395,7 @@ async def _run_script(
23152395
name=self.LOAD_SKILL_TOOL_NAME,
23162396
description="Loads the full instructions for a specific skill.",
23172397
func=_load,
2318-
approval_mode="always_require",
2398+
approval_mode=self._approval_mode(self._disable_load_skill_approval),
23192399
input_model={
23202400
"type": "object",
23212401
"properties": {
@@ -2328,7 +2408,7 @@ async def _run_script(
23282408
name=self.READ_SKILL_RESOURCE_TOOL_NAME,
23292409
description=("Reads a resource associated with a skill, such as references, assets, or dynamic data."),
23302410
func=_read_resource,
2331-
approval_mode="always_require",
2411+
approval_mode=self._approval_mode(self._disable_read_skill_resource_approval),
23322412
input_model={
23332413
"type": "object",
23342414
"properties": {
@@ -2345,7 +2425,7 @@ async def _run_script(
23452425
name=self.RUN_SKILL_SCRIPT_TOOL_NAME,
23462426
description="Runs a script associated with a skill.",
23472427
func=_run_script,
2348-
approval_mode="always_require",
2428+
approval_mode=self._approval_mode(self._disable_run_skill_script_approval),
23492429
input_model={
23502430
"type": "object",
23512431
"properties": {

0 commit comments

Comments
 (0)