Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
4cb375b
Updated mcp to 2.x (websocket optional is part of the core now)
jpalvarezl Sep 30, 2026
9fc5821
Added client test for agents as mcp_servers
jpalvarezl Sep 30, 2026
9ca48e2
More casing fixes
jpalvarezl Sep 30, 2026
e8135b0
Unit tests for legacy and v2 MCP server
jpalvarezl Sep 30, 2026
2ef1694
Using MCPError in samples for consistency with internal behaviour
jpalvarezl Sep 30, 2026
d02c0cd
Migrated test_mcp to use new types
jpalvarezl Sep 30, 2026
26abdee
collection for test_mcp
jpalvarezl Sep 30, 2026
9caaceb
collection passes for test_mcp_observability and test_mcp_skills
jpalvarezl Sep 30, 2026
bc759d4
Migrated message_handler
jpalvarezl Oct 1, 2026
af656e1
deprecated MCPWebsocketTool
jpalvarezl Oct 1, 2026
f6610a6
deprecated MCPWebsocketTool, part 2
jpalvarezl Oct 1, 2026
186d59b
documentation updates
jpalvarezl Oct 1, 2026
d1700ee
Removed MCP Tasks to start work on new mcp v2 API
jpalvarezl Oct 8, 2026
03ecd6f
Fix MCP v2 conversion error assertions
jpalvarezl Oct 9, 2026
c9b9616
Moved transport setup to mcp.Client, added test for v2. Needs legacy …
jpalvarezl Oct 2, 2026
c8108c6
Added legacy handshake unit test
jpalvarezl Oct 5, 2026
42b7e08
Added tool/call mock and assertions
jpalvarezl Oct 5, 2026
966bcb6
Adjusted sampling capabilities test
jpalvarezl Oct 5, 2026
e69a08f
removing ClientSession references in favour of using mcp.Client
jpalvarezl Oct 5, 2026
e3d3c5e
Assert preserve headers
jpalvarezl Oct 5, 2026
e4da143
Added session negotiation tests
jpalvarezl Oct 5, 2026
e0d439a
Sampling capabilities
jpalvarezl Oct 5, 2026
6cf3562
basic tools support completed
jpalvarezl Oct 6, 2026
6ff82f7
test cleanup
jpalvarezl Oct 6, 2026
7539779
Added support for ResourceLink for prompts
jpalvarezl Oct 6, 2026
e6da829
Added MCP client as a private field, set usage precedence 1st mcp_cli…
jpalvarezl Oct 7, 2026
2545c9d
Prompt migrated to use mcp_client -> session
jpalvarezl Oct 7, 2026
efbeccf
Migrated list/tools and list/prompts
jpalvarezl Oct 7, 2026
c16ee5f
Made protocol seam explicit with _MCPConnection
jpalvarezl Oct 7, 2026
521e1f0
Restore exact MCP v2 source tree
jpalvarezl Oct 9, 2026
e03d6a3
Complete MCP 2.x client compatibility foundation
jpalvarezl Oct 9, 2026
eabf6b3
Fix MCP v2 CI typing compatibility
jpalvarezl Oct 9, 2026
c6c4b06
Use httpx2 for MCP transport consumers
jpalvarezl Oct 9, 2026
d083137
Update declarative MCP lifecycle fixture
jpalvarezl Oct 9, 2026
322aa23
Use httpx2 in declarative MCP sample
jpalvarezl Oct 9, 2026
82f3244
Fix MCP optional import and timeout propagation
jpalvarezl Oct 9, 2026
7241a1e
Address MCP v2 server review feedback
jpalvarezl Oct 12, 2026
199b911
Address final MCP v2 review feedback
jpalvarezl Oct 12, 2026
09146e8
Restore read-only MCP client protocol property
jpalvarezl Oct 12, 2026
1c19068
Use Tenacity for MCP subscription recovery
jpalvarezl Oct 12, 2026
5fce018
Use compatible Tenacity dependency interval
jpalvarezl Oct 12, 2026
49ea549
Retry complete MCP subscription lifecycles
jpalvarezl Oct 12, 2026
28a0e04
Suppress expected WebSocket deprecation diagnostic
jpalvarezl Oct 12, 2026
c0b9fab
Moved tenacity to all and MCPTaskOptions to init
jpalvarezl Oct 13, 2026
89b6a74
Fixed seam leak for logging
jpalvarezl Oct 13, 2026
2a6eab2
fixed checked values for task_support
jpalvarezl Oct 13, 2026
0191611
changed meta and arguments to typed mappings
jpalvarezl Oct 13, 2026
0e5b888
Narrower types for subclasses
jpalvarezl Oct 13, 2026
a34c8bb
using right types for logging
jpalvarezl Oct 13, 2026
a4895a1
adjusted _skills.py types
jpalvarezl Oct 13, 2026
9923e52
Fix MCP v2 prompt refresh CI
jpalvarezl Oct 13, 2026
d28fc9b
re-removing MCPTaskOptions from init
jpalvarezl Oct 13, 2026
f1506c5
updated error message for tenacity installation
jpalvarezl Oct 13, 2026
6f69396
Added ping to the seam
jpalvarezl Oct 13, 2026
7e73bfd
ping from Client is also sent for legacy server scenarios
jpalvarezl Oct 13, 2026
a893b19
Fixes for CI
jpalvarezl Oct 13, 2026
91611a3
PR feedback: added MCPTaskOption deprecation warning shell and contex…
jpalvarezl Oct 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions python/PACKAGE_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,6 @@ listed below.
- `agent-framework-core`: experimental harness APIs for background agents, file access, looping,
memory, and file-backed todo storage under `agent_framework/_harness/`

#### `MCP_LONG_RUNNING_TASKS`

- `agent-framework-core`: `MCPTaskOptions` from `agent_framework/_mcp.py`

#### `MCP_SKILLS`

- `agent-framework-core`: `MCPSkillResource`, `MCPSkill`, and `MCPSkillsSource` from
Expand Down
5 changes: 2 additions & 3 deletions python/packages/ag-ui/agent_framework_ag_ui/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,15 @@
from typing import Any

from agent_framework import AgentResponseUpdate, ChatResponseUpdate, Content, FunctionTool
from agent_framework import _mcp as _core_mcp # pyright: ignore[reportPrivateUsage]
from agent_framework._serialization import make_json_safe # pyright: ignore[reportPrivateUsage]
from agent_framework._serialization import make_json_safe


def _mcp_tool_result_host_payload_key(core_mcp: Any) -> str:
"""Resolve the private core marker while supporting older core packages."""
return getattr(core_mcp, "_MCP_TOOL_RESULT_HOST_PAYLOAD_KEY", "_mcp_tool_result_host_payload")


_MCP_TOOL_RESULT_HOST_PAYLOAD_KEY = _mcp_tool_result_host_payload_key(_core_mcp)
_MCP_TOOL_RESULT_HOST_PAYLOAD_KEY = _mcp_tool_result_host_payload_key(None)
_AGUI_TOOL_RESULT_MODEL_CONTENT_KEY = "_agentFrameworkModelContent"
_AGUI_MCP_TOOL_RESULT_KEY = "_agentFrameworkMcpResult"
_AGUI_TOOL_RESULT_HOST_PAYLOAD_KEY = "_agentFrameworkHostPayload"
Expand Down
21 changes: 10 additions & 11 deletions python/packages/core/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,26 +209,25 @@ The vector store API is experimental under the shared `VECTOR_STORES` feature ID

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

- **`MCPTool`** - Base wrapper that owns the MCP `ClientSession` and exposes the remote server's tools as `FunctionTool`s.
- **`MCPStdioTool`** / **`MCPStreamableHTTPTool`** / **`MCPWebsocketTool`** - Transport-specific subclasses.
- **`MCPTool` connection state** - The private `_MCPConnection` Protocol is the normalized tools/prompts/catalog
surface. `_ClientMCPConnection` and `_SessionMCPConnection` each own one lifecycle responsibility; `None` means
disconnected, and the Client implementation is the single update point for SDK cache policy. `MCPTool.session`
remains a compatibility view/setter; constructor or direct assignment of a session selects the caller-owned
low-level path.
- **`MCPStdioTool`** / **`MCPStreamableHTTPTool`** - Supported transport-specific subclasses.
**`MCPWebsocketTool`** remains only as a deprecated compatibility symbol because MCP v2 removed WebSocket
transport; it cannot create a connection.
- **Argument allowlist (`_prepare_call_kwargs`)** - Before each `tools/call`, kwargs are filtered to an **allowlist** built from the tool's declared parameters (`inputSchema.properties`) plus any user-configured extras. **The declared half comes from the server's advertised schema**, and runtime kwargs (`FunctionInvocationContext.kwargs`, seeded from `function_invocation_kwargs`) are merged with the model-supplied arguments upstream in `_call_tool_with_runtime_kwargs`, so provenance is gone by the time the filter runs. A runtime kwarg is therefore forwarded whenever the server declares a property of that name, without the model mentioning it — the server, not the caller, decides which runtime kwarg names it receives. A tool that declares no usable `properties` (including schemas with `additionalProperties: true`) forwards only the configured extras. `_MCP_FRAMEWORK_DENYLIST` is a narrow safety net covering only non-serializable framework objects a server *declares* in its schema (those are dropped); it does not generalize to arbitrary caller-chosen names, and explicit extras always win. The reserved `_meta` key is never forwarded as an argument; trusted caller/runtime `_meta` is validated as MCP request metadata, model-supplied `_meta` is discarded in generated MCP functions, and metadata precedence is caller/runtime < OpenTelemetry < tools/list metadata.
- **`allowed_tools`** (constructor arg on all `MCPTool` subclasses) - Restricts exposed MCP tools by raw remote MCP tool identity. Prefixed local names remain accepted only when the raw remote name already matches its normalized form; normalized/local aliases do not authorize a different raw remote name. Configured allow/approval names must identify at most one raw remote name across loaded tools and prompts: a raw name that overlaps another tool's prefixed alias raises `ToolExecutionException`. Discovery validates all pages before publishing new functions; a failed reload retains the previous functions and metadata. The allowlist is also revalidated when exposing functions so runtime changes cannot select an ambiguous name. If multiple raw remote tool names map to the same local function name, tool loading raises `ToolExecutionException` instead of first-one-wins shadowing.
- **Progressive MCP disclosure** (`use_progressive_disclosure`, `always_load`) - When enabled on any `MCPTool` subclass, the initial model-facing surface is loader tools (`list_mcp_tools` / `load_tool` / `unload_tool`, prefixed by `tool_name_prefix` when configured) plus allowed tools selected by `always_load` and tools loaded earlier on the same `MCPTool` instance. `list_mcp_tools` only reports tools that pass `allowed_tools`; filtered tools are not listed or loadable. Loader tool names are reserved in progressive mode: remote MCP tools whose local generated name collides with a loader name are omitted from the initial/listed surface, and explicit `load_tool` calls return a model-visible message pointing callers to `tool_name_prefix` or excluding the colliding tool. `load_tool` accepts one tool name or a list of tool names and uses `FunctionInvocationContext.add_tools(...)` so the selected generated MCP `FunctionTool`s become available on the next function-calling iteration while keeping existing approval mode, argument filtering, header-provider runtime kwargs, result parsing, OTel, and task behavior. `unload_tool` accepts one dynamically loaded tool name or a list of names and removes them from the live tool list and persisted progressive surface, but it does not remove tools configured in `always_load`. Invalid `always_load` entries are ignored like unmatched `allowed_tools` entries.
- **Progressive MCP disclosure** (`use_progressive_disclosure`, `always_load`) - When enabled on any `MCPTool` subclass, the initial model-facing surface is loader tools (`list_mcp_tools` / `load_tool` / `unload_tool`, prefixed by `tool_name_prefix` when configured) plus allowed tools selected by `always_load` and tools loaded earlier on the same `MCPTool` instance. `list_mcp_tools` only reports tools that pass `allowed_tools`; filtered tools are not listed or loadable. Loader tool names are reserved in progressive mode: remote MCP tools whose local generated name collides with a loader name are omitted from the initial/listed surface, and explicit `load_tool` calls return a model-visible message pointing callers to `tool_name_prefix` or excluding the colliding tool. `load_tool` accepts one tool name or a list of tool names and uses `FunctionInvocationContext.add_tools(...)` so the selected generated MCP `FunctionTool`s become available on the next function-calling iteration while keeping existing approval mode, argument filtering, header-provider runtime kwargs, result parsing, and OTel. `unload_tool` accepts one dynamically loaded tool name or a list of names and removes them from the live tool list and persisted progressive surface, but it does not remove tools configured in `always_load`. Invalid `always_load` entries are ignored like unmatched `allowed_tools` entries.
- **Tool discovery refresh** - Validate the complete current tool snapshot alongside retained non-tool functions, not stale tool wrappers. Successful refreshes atomically replace the previous tool set and metadata, including empty snapshots, while preserving prompts, custom functions, and unchanged tool wrappers (including caller customizations). Removed or replaced tool identities lose their persisted progressive-loaded state; failed refreshes preserve the previous state.
- **`additional_tool_argument_names`** (constructor arg on all `MCPTool` subclasses) - Opt extra argument names back into the allowlist. Accepts a `Sequence[str]` (applied to every tool) or a `Mapping[str, Sequence[str]]` keyed by **remote tool name**, where the reserved key `"*"` denotes global extras. It is configured only in user code at construction; there is **no per-call/runtime override**, so a model-issued tool call cannot change which names pass through — but note this constrains the *model*, not the *server*, which still widens the effective allowlist through its schema. To use a server that accepts `additionalProperties: true`, list the extra names here and then either (1) manually extend that tool's `inputSchema` (via the `.functions` list after connecting) so the model is prompted to supply them, or (2) supply the values yourself via `function_invocation_kwargs`. If a normal forwarded argument name is supplied by both the model and `function_invocation_kwargs`, the model-supplied value wins; `_meta` is the exception and only trusted runtime/caller metadata is used.
- **MCP HTTP header request scoping** - `static_headers` supplies fixed, origin-scoped headers without serializing concurrent calls. Generated tool and prompt calls give `header_provider` only host runtime kwargs, separately from model arguments; direct `call_tool` calls give it caller kwargs. Model-over-runtime precedence is confined to outbound tool arguments. Fixed and dynamic headers form the session's complete effective identity (case-insensitive names, case-sensitive values), with dynamic values overriding fixed values of the same name. Agent runs reconcile a connected tool before copying its functions; generated tool and prompt calls perform the same check at invocation. Connected run preparation defers when a provider needs runtime values supplied by invocation middleware; invocation-time resolution remains strict. A different identity waits for in-flight discovery, reconnects framework-created sessions without loading, then refreshes session-derived discovery under the same lock. Because a caller-supplied session's established identity is unknown and the wrapper cannot reconnect it, dynamic header resolution on that wrapper is rejected even while disconnected. Ambient requests retain the bound set. With a shared `http_client`, processing stays scoped to the originating `MCPStreamableHTTPTool`, and every injected header is stripped from cross-origin redirects. The session exit stack removes its request hook after transport shutdown, including failed initialization or discovery, and closes framework-created HTTP clients. Failed discovery resets the connection and discovery flags and rolls back partial function/metadata additions. Framework-created sessions are discarded; constructor-supplied sessions remain caller-owned and reusable across cleanup, close, and reset.
- **MCP lifecycle caller cancellation** - The lifecycle owner skips cancelled queued connect requests and waits for the caller to acknowledge successful setup. If the caller cancels before accepting a newly established connection, the owner tears it down before processing the next request. Cancelling a close waiter does not interrupt teardown, and cancelling a redundant connect does not discard a previously established session.
- **Streamable HTTP cookies** - Framework-created clients reject response-cookie persistence with or without a `header_provider`; explicit `Cookie` headers from `static_headers` or a provider remain supported. Caller-provided clients retain their cookie behavior and ownership. Applications requiring cookie persistence must scope clients and MCP sessions to one authenticated principal. Cookie rejection does not isolate MCP protocol sessions or other server-side state.
- **`function_invocation_kwargs` and MCP servers** - That dict is shared across every tool in the run, including every attached `MCPTool`, and any name in it reaches a server that declares a matching `inputSchema` property. `header_provider` does not mitigate this — it reads the kwargs without consuming them. To keep a credential out of tool arguments, source it outside `function_invocation_kwargs`: read a `ContextVar` inside the provider (this still allows a different value per request), configure a custom `http_client`, or use `env` for `MCPStdioTool`.
- **Sampling guardrails** (`sampling_callback`) - Passing `client=` advertises `SamplingCapability` so the server can send `sampling/createMessage`. Because remote servers are untrusted (confused-deputy risk), the default `sampling_callback` is **deny-by-default** and applies, in order: a per-session rate limit (`sampling_max_requests`, default `_DEFAULT_SAMPLING_MAX_REQUESTS`), an approval gate (`sampling_approval_callback`), and a `maxTokens` cap (`sampling_max_tokens`, default `_DEFAULT_SAMPLING_MAX_TOKENS`). The approval callback (constructor arg on all subclasses; exported type alias `SamplingApprovalCallback`) receives the raw `CreateMessageRequestParams`, may be sync or async, and must return truthy to approve. When it is `None` (the default) every sampling request is denied; pass `lambda params: True` to restore legacy auto-approve as an explicit opt-in. Requests and denials are logged at WARNING (content is not logged). The per-session counter resets in `_reset_session_state`.
- **`MCPTaskOptions`** (experimental, `MCP_LONG_RUNNING_TASKS` feature, **frozen**) - Per-tool-instance options controlling the SEP-2663 long-running task lifecycle. When the server advertises a tool with `execution.taskSupport == "required"`, `MCPTool.call_tool` transparently routes through `call_tool_as_task`, which sends an augmented `tools/call`, polls `tasks/get` until terminal, and reinterprets `tasks/result` as a normal `CallToolResult`. Instances are immutable; replace via `MCPTool.task_options = MCPTaskOptions(...)`. Fields:
- `default_ttl: timedelta | None` — forwarded to the server as `params.task.ttl` (milliseconds). When `None`, the server's default applies.
- `cancel_remote_task_on_local_cancellation: bool = True` — only gates the `CancelledError` path. Abandonment paths (see below) always cancel.
- `max_task_wait: timedelta | None` — client-side deadline for the whole post-create lifecycle (poll + result fetch). When exceeded, raises `ToolExecutionException` and fires a best-effort `tasks/cancel`. `None` (default) means no client-side bound. Bounds sleeps, sends, AND reconnects via `asyncio.wait_for`.
- **Permissive fallback**: servers that ignore the augmentation (return `CallToolResult` directly) or reject the unknown `task` field with `METHOD_NOT_FOUND` / `INVALID_PARAMS` fall back to the plain `session.call_tool(...)` path so legacy servers keep working. An unparseable success response (server accepted the augmented call but returned a payload that is neither `CreateTaskResult` nor `CallToolResult`) **does not** fall back — it raises `ToolExecutionException` to avoid double-executing a side-effecting tool.
- **Submit-vs-track reconnect policy**: a dropped connection before a `task_id` is known raises `ToolExecutionException("connection lost; task state unknown")` without re-issuing the augmented `tools/call`, so a server that accepted the request but lost the response cannot be made to start the same operation twice; once a `task_id` exists, `tasks/get` / `tasks/result` reconnect once and retry against the same id (a shared `_send_with_one_reconnect` helper).
- **Cancel-on-abandonment vs terminal failure**: any path where the remote task may still be running (max-wait exceeded, hard `McpError` in poll, malformed `tasks/get`, second connection loss in poll/fetch, reconnect failure) fires best-effort `tasks/cancel` before raising. Terminal failures (`failed`/`cancelled`/`input_required` server-side, `completed+isError`, malformed `tasks/result` after server completed) do **not** cancel — the server is already done. `_MCPTaskAbandoned` is the private marker distinguishing the two.
- **Transient poll retry**: a slow `tasks/get` that surfaces as `McpError(code=408 REQUEST_TIMEOUT)` is retried (bounded by `max_task_wait`). All other non-connection `McpError`s during poll are treated as abandonment. `tasks/result` does not get transient retry — the server has already completed, so a slow payload fetch is anomalous.
- **MCP Tasks extension** - MCP Python SDK 2.2 does not implement the 2026 `io.modelcontextprotocol/tasks` runtime. Tools that advertise `execution.taskSupport == "required"` are skipped with a warning; optional and task-free tools continue through ordinary `tools/call`. Restoration of the experimental Tasks API and removed sample is tracked in microsoft/agent-framework#8245.

### File Access Harness (`_harness/_file_access.py`)

Expand Down
Loading
Loading