Metadata-Version: 2.5
Name: donkey-kit
Version: 0.2.0
Summary: An SDK for consuming Agent Fabric capabilities (governed model and tool access) from your own agent framework.
Project-URL: Homepage, https://github.com/Donkey-Development-Kit/donkey-development-kit
Project-URL: Documentation, https://docs.donkey-kit.dev
Project-URL: Repository, https://github.com/Donkey-Development-Kit/donkey-development-kit
Project-URL: Issues, https://github.com/Donkey-Development-Kit/donkey-development-kit/issues
Project-URL: Changelog, https://github.com/Donkey-Development-Kit/donkey-development-kit/releases
Author: Donkey Development Kit maintainers
Maintainer: Donkey Development Kit maintainers
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: agent,anypoint,donkey-kit,gateway,llm,mcp,mulesoft
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: tomli>=2.0; python_version < '3.11'
Requires-Dist: typing-extensions>=4.10; python_version < '3.12'
Provides-Extra: a2a
Requires-Dist: a2a-sdk[http-server]>=1.2.0; extra == 'a2a'
Requires-Dist: uvicorn>=0.30; extra == 'a2a'
Provides-Extra: adk
Requires-Dist: google-adk>=2.4; extra == 'adk'
Requires-Dist: litellm[mcp]>=1.84; extra == 'adk'
Provides-Extra: agent-framework
Requires-Dist: agent-framework>=1.13; extra == 'agent-framework'
Provides-Extra: all
Requires-Dist: a2a-sdk[http-server]>=1.2.0; extra == 'all'
Requires-Dist: langchain-core>=0.3.45; extra == 'all'
Requires-Dist: langchain-openai>=0.3.9; extra == 'all'
Requires-Dist: langgraph>=0.4; extra == 'all'
Requires-Dist: mcp>=1.24; extra == 'all'
Requires-Dist: openai>=1.66; extra == 'all'
Requires-Dist: opentelemetry-api>=1.24; extra == 'all'
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.24; extra == 'all'
Requires-Dist: opentelemetry-sdk>=1.24; extra == 'all'
Requires-Dist: rich>=13.8.0; extra == 'all'
Requires-Dist: starlette>=0.37; extra == 'all'
Requires-Dist: typer>=0.26.0; extra == 'all'
Requires-Dist: uvicorn>=0.30; extra == 'all'
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.116; extra == 'anthropic'
Provides-Extra: cli
Requires-Dist: rich>=13.8.0; extra == 'cli'
Requires-Dist: typer>=0.26.0; extra == 'cli'
Provides-Extra: crewai
Requires-Dist: crewai>=1.15.3; extra == 'crewai'
Provides-Extra: langgraph
Requires-Dist: langchain-core>=0.3.45; extra == 'langgraph'
Requires-Dist: langchain-openai>=0.3.9; extra == 'langgraph'
Requires-Dist: langgraph>=0.4; extra == 'langgraph'
Provides-Extra: llamaindex
Requires-Dist: llama-index-llms-openai-like>=0.8; extra == 'llamaindex'
Provides-Extra: llm
Requires-Dist: openai>=1.66; extra == 'llm'
Provides-Extra: local
Requires-Dist: starlette>=0.37; extra == 'local'
Requires-Dist: uvicorn>=0.30; extra == 'local'
Provides-Extra: mcp
Requires-Dist: mcp>=1.24; extra == 'mcp'
Provides-Extra: openai-agents
Requires-Dist: openai-agents>=0.20; extra == 'openai-agents'
Requires-Dist: openai>=2.45; extra == 'openai-agents'
Provides-Extra: otel
Requires-Dist: opentelemetry-api>=1.24; extra == 'otel'
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.24; extra == 'otel'
Requires-Dist: opentelemetry-sdk>=1.24; extra == 'otel'
Provides-Extra: strands
Requires-Dist: strands-agents[openai]>=1.57.1; extra == 'strands'
Provides-Extra: test
Requires-Dist: pytest-asyncio>=0.23.1; extra == 'test'
Requires-Dist: pytest>=8.0; extra == 'test'
Description-Content-Type: text/markdown

<p align="center">
  <img src="https://raw.githubusercontent.com/Donkey-Development-Kit/donkey-development-kit/main/brand/ddk-logo-stacked-black.png" alt="Donkey Development Kit (DDK)" width="180" />
</p>

<p align="center">
  <a href="https://pypi.org/project/donkey-kit/"><img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fpypi.org%2Fpypi%2Fdonkey-kit%2Fjson&query=%24.info.version&prefix=v&label=PyPI&logo=pypi&logoColor=white&color=blue&cacheSeconds=300" alt="donkey-kit on PyPI" /></a>
  <a href="https://pypi.org/project/donkey-kit/"><img src="https://img.shields.io/pypi/pyversions/donkey-kit?logo=python&logoColor=white" alt="Supported Python versions" /></a>
  <a href="https://github.com/Donkey-Development-Kit/donkey-development-kit/actions/workflows/ci.yml?query=branch%3Adevelop"><img src="https://img.shields.io/github/actions/workflow/status/Donkey-Development-Kit/donkey-development-kit/ci.yml?branch=develop&label=CI&logo=githubactions&logoColor=white" alt="CI status" /></a>
  <a href="https://pepy.tech/projects/donkey-kit"><img src="https://img.shields.io/pepy/dt/donkey-kit?label=downloads" alt="Total downloads" /></a>
  <a href="https://github.com/Donkey-Development-Kit/donkey-development-kit/blob/main/LICENSE"><img src="https://img.shields.io/pypi/l/donkey-kit?color=green" alt="License: Apache-2.0" /></a>
  <a href="https://docs.donkey-kit.dev"><img src="https://img.shields.io/badge/docs-docs.donkey--kit.dev-0A7BBB?logo=readthedocs&logoColor=white" alt="Documentation" /></a>
</p>

<p align="center">
  <a href="https://docs.donkey-kit.dev/frameworks/langgraph"><img src="https://img.shields.io/badge/LangGraph-1C3C3C?logo=langgraph&logoColor=white" alt="LangGraph" /></a>
  <a href="https://docs.donkey-kit.dev/frameworks/adk"><img src="https://img.shields.io/badge/Google_ADK-4285F4?logo=google&logoColor=white" alt="Google ADK" /></a>
  <a href="https://docs.donkey-kit.dev/frameworks/openai"><img src="https://img.shields.io/badge/OpenAI_Agents_SDK-412991" alt="OpenAI Agents SDK" /></a>
  <a href="https://docs.donkey-kit.dev/frameworks/agent-framework"><img src="https://img.shields.io/badge/Microsoft_Agent_Framework-5E5E5E" alt="Microsoft Agent Framework" /></a>
  <a href="https://docs.donkey-kit.dev/frameworks/anthropic"><img src="https://img.shields.io/badge/Anthropic-191919?logo=anthropic&logoColor=white" alt="Anthropic" /></a>
  <a href="https://docs.donkey-kit.dev/frameworks/crewai"><img src="https://img.shields.io/badge/CrewAI-FF5A50?logo=crewai&logoColor=white" alt="CrewAI" /></a>
  <a href="https://docs.donkey-kit.dev/frameworks/llamaindex"><img src="https://img.shields.io/badge/LlamaIndex-8A2BE2" alt="LlamaIndex" /></a>
  <a href="https://docs.donkey-kit.dev/frameworks/strands"><img src="https://img.shields.io/badge/Strands_Agents-FF9900" alt="Strands Agents" /></a>
  <a href="https://docs.donkey-kit.dev/telemetry"><img src="https://img.shields.io/badge/OpenTelemetry-425CC7?logo=opentelemetry&logoColor=white" alt="OpenTelemetry" /></a>
</p>

# Donkey Development Kit

An SDK for consuming **Agent Fabric** capabilities — governed model and tool
access — from your own agent framework, in your own IDE, without adopting Mule.

> **Project status — alpha.** This is an early release
> (`Development Status :: 3 - Alpha`). The **LLM data plane is live-verified**;
> most other surfaces are verification-gated (see
> [What's verified](#whats-verified-verification-discipline) below). Install it
> from PyPI with `pip install donkey-kit` — see [Install](#install).
> **Unofficial:** an independent project,
> **not** affiliated with or endorsed by Salesforce or MuleSoft.

> ### Support & trademark statement (please read)
>
> **"Agent Fabric" is a MuleSoft (Salesforce) product name, not a generic
> term.** `MuleSoft`, `Anypoint`, `Omni Gateway`, and `Agent Fabric` are
> Salesforce trademarks.
>
> **Maintainer & support.** This is an **independent, community-maintained**
> project, published under the org-scoped `Donkey-Development-Kit` name — it is **not**
> affiliated with, endorsed by, or supported by Salesforce or MuleSoft. It is
> provided **as-is, without warranty of any kind**; the maintainers triage issues
> and pull requests on a **best-effort basis, with no SLA**. Because it ships
> under a distinct, org-scoped name, only the descriptive form ("an SDK for
> MuleSoft Agent Fabric") appears in prose — the package does not represent itself
> as a first-party, official-status SDK.
>
> Licensed under [Apache-2.0](https://github.com/Donkey-Development-Kit/donkey-development-kit/blob/main/LICENSE). See
> the [unsupported boundary](https://docs.donkey-kit.dev/reference/unsupported-boundary) for exactly
> which platform APIs this SDK calls and their support classification.
>
> **Security.** Report vulnerabilities privately, never in a public issue. See
> [`SECURITY.md`](https://github.com/Donkey-Development-Kit/donkey-development-kit/blob/main/SECURITY.md) for supported versions and how to report.
>
> **Python versions.** CPython 3.10–3.14, each tested in CI; a version is
> dropped in the first minor release after its end of life. See
> [`docs/python-support.md`](https://github.com/Donkey-Development-Kit/donkey-development-kit/blob/main/docs/python-support.md).

<!-- toc -->
**Contents**

- [Documentation](#documentation)
- [Install](#install)
- [Framework support](#framework-support)
- [What's verified (verification discipline)](#whats-verified-verification-discipline)
- [Conformance exemptions](#conformance-exemptions)
<!-- tocstop -->

## Documentation

Two audiences, two doc sets:

- **Use the SDK** → the documentation site:
  **<https://docs.donkey-kit.dev/>**. Install and
  configure, per-framework model access, the governed error taxonomy, and what
  to trust today — everything you need to point your agent at a governed proxy.
- **See it run** → runnable demos live in the companion repo
  **[donkey-development-kit-demos](https://github.com/Donkey-Development-Kit/donkey-development-kit-demos)**:
  the framework-free client, native framework objects, the governed error
  taxonomy, and the screen-recording scripts.
- **Understand or contribute to the repo:**
  - [`ARCHITECTURE.md`](https://github.com/Donkey-Development-Kit/donkey-development-kit/blob/main/ARCHITECTURE.md) — how the SDK is built: the layered
    stack, the framework-free core, verification discipline, the error taxonomy,
    and framework tiering.
  - [`CONTRIBUTING.md`](https://github.com/Donkey-Development-Kit/donkey-development-kit/blob/main/CONTRIBUTING.md) — how to work in the repo: the
    branch/PR/release workflow, the testing strategy, coding conventions, and
    the docs-sync rule.
  - [`docs/verified-apis.md`](https://github.com/Donkey-Development-Kit/donkey-development-kit/blob/main/docs/verified-apis.md) — the verification ledger:
    the single source of truth for what is confirmed against a real sandbox and
    what is still blocked.

## Install

```bash
pip install "donkey-kit[llm,langgraph]"   # base + raw client + one framework
```

To work on the SDK itself, install from source with the contributor tooling
(the `dev` dependency group needs pip 25.1 or later):

```bash
git clone https://github.com/Donkey-Development-Kit/donkey-development-kit.git
cd donkey-development-kit/python
pip install -e ".[llm,cli]" --group dev
```

Extras are one per framework (`langgraph`, `adk`, `strands`, `agent_framework`,
`openai-agents`, `anthropic`, `crewai`, `llamaindex`) plus `otel`, `cli`, `local`,
`a2a` (the official `a2a-sdk`, for `donkey.serve()`), `mcp` (the MCP client a
governed `ToolSet` binds with), `test` (the [conformance pytest plugin](https://docs.donkey-kit.dev/testing) —
`pytest --donkey-conformance --donkey-agent=my_app.agent:build`), and `all`. `all` is
everything a user runs that installs together — `llm`, `langgraph`, `otel`, `cli`,
`local`, `a2a`, `mcp` — with no test runner, so add `test` for the conformance plugin:
`donkey-kit[all,test]`. It leaves out the seven other framework extras, because
whether they install together depends on their upstream releases. The generated
[co-installability table](https://github.com/Donkey-Development-Kit/donkey-development-kit/blob/main/docs/co-installability.md) shows which combinations resolve, re-checked
nightly. Add the one framework you use: `donkey-kit[all,crewai]`.
Configuration and first-agent walkthroughs live on the
[documentation site](https://docs.donkey-kit.dev/). Features that call the Anypoint
control plane (registry, tool discovery, `donkey publish`) need a connected app
with the scopes listed per operation in [`docs/scopes.md`](https://github.com/Donkey-Development-Kit/donkey-development-kit/blob/main/docs/scopes.md).

## Framework support

The roster is deliberately **two deep, six shallow** (`BG §1.8`): LangGraph and
the OpenAI Agents SDK are held to the full conformance bar, the rest supported
through the three-line `connection_kwargs()` escape hatch. Every framework below returns its framework's
**own native object** — never a wrapper.

<!-- BEGIN GENERATED: framework-status (edit docs/verified-apis.md §8, then run python/scripts/generate_docs.py) -->
Each factory's status is its constructor row in the [`docs/verified-apis.md` §8](https://github.com/Donkey-Development-Kit/donkey-development-kit/blob/main/docs/verified-apis.md); its
depth is the adapter roster's `conformance_tested`.

| Framework | Factory | Depth | Ledger status |
|---|---|---|---|
| LangGraph | `donkey.langgraph.chat_model()` | **Conformance-tested** | **Live-verified** |
| Google ADK | `donkey.adk.model()` | `connection_kwargs()` | **Live-verified** |
| Google ADK | `donkey.adk.gemini()` | `connection_kwargs()` | **Live-verified** |
| Strands | `donkey.strands.model()` | `connection_kwargs()` | **Live-verified** |
| MS Agent Framework | `donkey.agent_framework.chat_client()` | `connection_kwargs()` | **Live-verified** |
| OpenAI Agents SDK | `donkey.openai_agents.model()` | **Conformance-tested** | **Live-verified** |
| Anthropic SDK | `donkey.anthropic.client()` | `connection_kwargs()` | **Live-verified** |
| CrewAI | `donkey.crewai.llm()` | `connection_kwargs()` | **Live-verified** |
| LlamaIndex | `donkey.llamaindex.llm()` | `connection_kwargs()` | **Live-verified** |
<!-- END GENERATED: framework-status -->

**Signature-confirmed** means the factory builds its native object against the
installed framework (`scripts/verify_frameworks.py`), with no live round-trip.
**Live-verified** means a real round-trip through a governed proxy. The raw
client (`donkey.llm.client()`) is conformance-tested too.

`connection_kwargs()` works for all eight; a deep adapter is promoted from demand
evidence, one at a time — never guessed up front. The OpenAI Agents SDK was the
second, on Phase 1 demand (#223); the next is #244. See the
[framework pages](https://docs.donkey-kit.dev/frameworks/)
for each. Which framework versions are supported and tested is on the site's
[version compatibility](https://docs.donkey-kit.dev/frameworks/compatibility)
page and in [`docs/framework-support.md`](https://github.com/Donkey-Development-Kit/donkey-development-kit/blob/main/docs/framework-support.md).

## What's verified (verification discipline)

The **LLM data plane** — governed model access through the Omni Gateway proxy —
is live-verified against a real Anypoint sandbox. The framework-free client and
the framework adapters are wired to that contract; the raw client is
conformance-tested against the simulator. For the adapters (each factory is
listed under [Framework support](#framework-support)):

<!-- BEGIN GENERATED: framework-status-summary (edit docs/verified-apis.md §8, then run python/scripts/generate_docs.py) -->
LangGraph and OpenAI Agents SDK are conformance-tested against the simulator in CI.
Each adapter factory's constructor row in §8 of the ledger reads **live-verified**.
<!-- END GENERATED: framework-status-summary -->

Everything still gated raises `NotImplementedError("blocked on verification: …")`
rather than guessing at an unverified endpoint, header, or class name. That
currently covers a `Publication`'s descriptor derivation (`preview()`, `export()`),
the lockfile (`donkey.tools.lock()`, `discover(locked=True)`), the governed-state
search filters (`search(governed=, environment=, domain=)`) and the hidden
`donkey status` command. Exchange search and the publication of scanned MCP
servers and agents (`donkey publish`, `donkey verify`) are verified against a
live sandbox. MCP tool discovery and every framework's `ToolSet` binding are
verified through the governed gateway: behind Client ID Enforcement all eight
frameworks called a bound tool (#1312); behind JWT Validation seven did and
ADK's tools were listed but not called (#221). Their types (`ToolSet`,
`GovernanceCriteria`, `UngovernedMcpServer`, …) are exported from `donkey_kit`. The SDK
does not ship a provisioning control plane (ADR 0008 in `docs/adr/`). The adapters build their framework's native object directly; they
refuse only when the installed framework version lacks the class or field the
adapter depends on.

The discipline behind this is documented in
[`ARCHITECTURE.md` → Verification discipline](https://github.com/Donkey-Development-Kit/donkey-development-kit/blob/main/ARCHITECTURE.md#verification-discipline);
the row-by-row worklist is [`docs/verified-apis.md`](https://github.com/Donkey-Development-Kit/donkey-development-kit/blob/main/docs/verified-apis.md).

## Conformance exemptions

The [conformance plugin](https://docs.donkey-kit.dev/testing)
holds the SDK to the same bar it asks of your agent. Where a framework
legitimately cannot satisfy a scenario, the reason is asserted in code
(`KNOWN_LIMITATIONS`) and published here as credibility — never a silent skip
(the conformance kit):

| Framework | Scenario | Why it's exempt |
| --- | --- | --- |
| CrewAI | correlation ID propagated | CrewAI's native OpenAI provider builds both its sync `OpenAI` and its `AsyncOpenAI` from one `client_params` dict, and with an interceptor set it replaces `http_client` with its own `httpx` client, so the SDK's async client cannot be injected and the correlation ID ends up per-client, not per-run. ADK (`model()` and `gemini()`), LlamaIndex and Microsoft Agent Framework send through the SDK's shared client and record no exemption (#691, #740). |
| CrewAI | gateway identity observed | For the same reason, no response reaches the SDK's `_on_response` hook. When every resolved adapter is non-observing, `donkey.last_call` reports `UNAVAILABLE` and names them in `surface`. |
| CrewAI | JWT refreshed per send | CrewAI's native OpenAI provider owns the transport and builds its own clients, so the rotating JWT the SDK adds per send never reaches its requests. `donkey.crewai.llm()` and `connection_kwargs()` raise `ConfigError` in `jwt` mode; use client-id auth with CrewAI. ADK's `model()` and `gemini()`, LlamaIndex and Microsoft Agent Framework send through the SDK's client and carry the rotating JWT on async calls ([`jwt` mode](https://docs.donkey-kit.dev/reference/configuration#jwt--model-wallet-auth-mode)). |
| CrewAI | budget refusal not retried | CrewAI wraps every LLM call in its own rate-limit retry (3 attempts) and treats any `429` as a rate limit, so a `TokenBudgetExceeded` or `RequestRateLimitExceeded` refusal is sent 3 times. CrewAI has no setting to turn it off; the OpenAI client underneath has `max_retries=0`. Every other adapter sends a budget refusal once (#734). |
| CrewAI | typed refusal bridged | CrewAI's native OpenAI provider owns the transport, so its errors come from a client the SDK never saw, and `donkey.run()` and `typed_refusals()` cannot tell a gateway refusal from any other failure there and pass those errors through (#724). ADK's `gemini()` and `model()` send through the SDK's transport and are bridged (conformance-tested in #955 and #969). LiteLLM, under `model()`, re-raises a refusal as its own exception, and the adapter's `llm_client` marks that exception with the refusal the SDK's transport received. |
| CrewAI | refusal handler cannot un-refuse | CrewAI's native OpenAI provider owns the transport, so its refusals never reach the SDK's `_on_refusal` hook and handlers registered with [`donkey.on(...)`](https://docs.donkey-kit.dev/refusal-handlers) do not run for its calls. Every other adapter runs them, and none can turn a refusal into a success (#208, #209). |
| CrewAI | custom policy typed | For the same reason, a refusal shape registered with [`classify.register()`](https://docs.donkey-kit.dev/custom-policies) is neither typed by `donkey.run()` nor seen by `donkey.on(...)` handlers for CrewAI's calls. Every other adapter raises the registered type, runs its handlers and records its slug as `donkey.policy.type` (#210). |
| CrewAI | agent kill stops cleanly | CrewAI's native OpenAI provider owns the transport, so a kill-switch refusal never reaches the SDK's transport: it does not stop [`donkey.budget.pace()`](https://docs.donkey-kit.dev/budget) loops or flush spans, and CrewAI's own rate-limit retry decides whether the call is re-sent. Every other adapter sends the kill once, stops every pace loop on the same `Donkey`, and flushes spans before the `AgentKilled` reaches your code (#220). |
| CrewAI | approval round trip | CrewAI's native OpenAI provider owns the transport, so when an approved [`@donkey.tool(approval="required")`](https://docs.donkey-kit.dev/hitl) resumes, its model call carries neither the approval's correlation ID nor a typed refusal. The pause and the approver's span event work as for any Python code. The round trip runs on the raw client and LangGraph (#211, #212), the OpenAI Agents SDK, which pauses on its own `needs_approval` and resumes from the `RunState` (#213), and Strands' and ADK's own pauses (#1234, #1235): each resumes the call under the decision's correlation ID, and the gateway still judges it, so a refusal comes back typed. |
| Google ADK | multi-server name collision | `ToolSet.adk()` binds each MCP server as one `McpToolset`, which takes an allow-list of tool names but cannot rename a single tool. When two servers expose a tool under the same name, the bind raises `ConfigError` before it connects; `filter(deny=["<server>__<tool>"])` keeps one of each. The LangGraph, LlamaIndex and Anthropic bindings bind each tool on its own and expose both as `<server>__<tool>` (#991). |
| Strands Agents | multi-server name collision | For the same reason: `ToolSet.strands()` binds each server as one `MCPClient`, filtered by `tool_filters`. |
| OpenAI Agents SDK | multi-server name collision | For the same reason: `ToolSet.openai_agents()` binds each server as one `MCPServerStreamableHttp`, filtered by a static `tool_filter`. Its model calls record no exemption. |
| Microsoft Agent Framework | multi-server name collision | For the same reason: `ToolSet.agent_framework()` binds each server as one `MCPStreamableHTTPTool`, filtered by `allowed_tools`. |
| CrewAI | multi-server name collision | For the same reason: `ToolSet.crewai()` binds each server as one `crewai_tools.MCPServerAdapter`, filtered by its `tool_names`. |
| Google ADK | MCP tool denial typed | `ToolSet.adk()` leaves each tool call to `McpToolset`, which turns the gateway's MCP access denial (JSON-RPC `-32008` "Unable to access") into an error result whose text has lost the code, so the SDK cannot raise [`ToolNotInProfile`](https://docs.donkey-kit.dev/tool-access/binding#tools-outside-your-access-profile) from it. |
| Strands Agents | MCP tool denial typed | For the same reason: `MCPClient` returns the denial as a `status: error` tool result without the code. |
| OpenAI Agents SDK | MCP tool denial typed | `ToolSet.openai_agents()` leaves each tool call to `MCPServerStreamableHttp`, which raises the `mcp` package's `McpError` with the code intact; `tool_access_denial(exc)` turns it into `ToolNotInProfile`. |
| Microsoft Agent Framework | MCP tool denial typed | For the same reason: `MCPStreamableHTTPTool` raises its `ToolExecutionException` with the `McpError` as its cause; `tool_access_denial(exc)` converts it. |
| CrewAI | MCP tool denial typed | For the same reason: `MCPServerAdapter`'s tools raise the `McpError`; `tool_access_denial(exc)` converts it. |

Each transport exemption matches what the adapter reports in `donkey.<framework>.capabilities()` (a frozen `AdapterCapabilities` per factory): an adapter is exempt from the transport scenarios exactly when it reports `transport="framework"`, from gateway identity exactly when `observes_last_call` is false, and from the typed-refusal bridge exactly when `typed_refusals` is false. A unit test keeps the two in step (#726). The refusal-handler, custom-policy and kill scenarios run against every factory that reports `transport="shared"`, and a factory added without a run fails the suite (#1441). The multi-server and MCP tool denial rows are about binding MCP tools with [`ToolSet`](https://docs.donkey-kit.dev/tool-access/binding), not the model transport, so `capabilities()` does not report them.

<!-- Generated from the repository README.md by python/scripts/generate_docs.py; edit that file, then run the script. -->
