Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
211 changes: 115 additions & 96 deletions python/packages/core/agent_framework/_mcp.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion python/packages/core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ dependencies = [

[project.optional-dependencies]
all = [
"mcp>=1.24.0,<2",
"mcp>=2.0.0,<3",
"agent-framework-a2a",
"agent-framework-ag-ui",
"agent-framework-anthropic",
Expand Down
568 changes: 284 additions & 284 deletions python/packages/core/tests/core/test_mcp.py

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
ReasoningSummaryPartBuilder,
TextContentBuilder,
)
from mcp import McpError
from mcp.shared.exceptions import MCPError
from typing_extensions import Any

from ._feature_usage import FeatureIndex
Expand Down Expand Up @@ -322,8 +322,8 @@ def consent_url_from_error(exc: BaseException) -> list[ConsentError] | None:
Returns:
The consent URL(s) extracted from the error, or ``None`` if no consent error was found.
"""
inner_exception = next((arg for arg in exc.args if isinstance(arg, McpError)), None)
if inner_exception is not None and inner_exception.error.code == CONSENT_ERROR_CODE:
inner_exception = next((arg for arg in exc.args if isinstance(arg, MCPError)), None)
if inner_exception is not None and inner_exception.code == CONSENT_ERROR_CODE:
# Parse the error message
# The error message is structured with the following format:
# "tools/list failed for 1 tool source(s), succeeded for 0 tool source(s) {"errors":[{"name": ..."
Expand All @@ -344,11 +344,11 @@ def consent_url_from_error(exc: BaseException) -> list[ConsentError] | None:
# ruff: enable[commented-out-code]
try:
consent_errors: list[ConsentError] = []
error_message_start = inner_exception.error.message.find("{")
error_message_start = inner_exception.message.find("{")
if error_message_start == -1:
logger.warning("Consent error message does not contain JSON: %s", inner_exception.error.message)
logger.warning("Consent error message does not contain JSON: %s", inner_exception.message)
return None
consent_details_json = inner_exception.error.message[error_message_start:]
consent_details_json = inner_exception.message[error_message_start:]
consent_details = json.loads(consent_details_json)
if "errors" not in consent_details or not isinstance(consent_details["errors"], list):
logger.warning("Consent error message JSON does not contain 'errors' list: %s", consent_details_json)
Expand All @@ -370,7 +370,7 @@ def consent_url_from_error(exc: BaseException) -> list[ConsentError] | None:
if consent_errors:
return consent_errors
except json.JSONDecodeError:
logger.warning("Failed to parse consent details JSON: %s", inner_exception.error.message)
logger.warning("Failed to parse consent details JSON: %s", inner_exception.message)
return None


Expand Down
2 changes: 1 addition & 1 deletion python/packages/foundry_hosting/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ dependencies = [
"azure-ai-agentserver-responses>=1.0.0b8,<2",
"azure-ai-agentserver-invocations>=1.0.0b6,<2",
"httpx>=0.28,<1",
"mcp>=1.24.0,<2",
"mcp>=2.0.0,<3",
]

[tool.uv]
Expand Down
11 changes: 5 additions & 6 deletions python/packages/foundry_hosting/tests/test_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@
)
from azure.ai.agentserver.responses import InMemoryResponseProvider, ResponseContext
from azure.ai.agentserver.responses.models import CreateResponse
from mcp import McpError
from mcp.types import ErrorData
from mcp.shared.exceptions import MCPError
from typing_extensions import Any

from agent_framework_foundry_hosting import (
Expand Down Expand Up @@ -4290,7 +4289,7 @@ def _make_consent_error(
]
})
message = f"tools/list failed for 1 tool source(s), succeeded for 0 tool source(s) {payload}"
inner = McpError(ErrorData(code=CONSENT_ERROR_CODE, message=message))
inner = MCPError(code=CONSENT_ERROR_CODE, message=message)
return ToolExecutionException("MCP consent required", inner_exception=inner)


Expand All @@ -4303,21 +4302,21 @@ def test_returns_none_when_no_mcp_error_in_args(self) -> None:
assert consent_url_from_error(Exception("boom")) is None

def test_returns_none_when_mcp_error_has_different_code(self) -> None:
inner = McpError(ErrorData(code=-32000, message="some other error"))
inner = MCPError(code=-32000, message="some other error")
exc = Exception("wrapped", inner)
assert consent_url_from_error(exc) is None

def test_returns_none_for_bare_mcp_error_without_wrapping(self) -> None:
# `args` of a bare McpError holds the message string, not an McpError
# instance, so it does not match the wrapping pattern produced by the
# MCP client when it bubbles consent errors up.
bare = McpError(ErrorData(code=CONSENT_ERROR_CODE, message="https://x"))
bare = MCPError(code=CONSENT_ERROR_CODE, message="https://x")
assert consent_url_from_error(bare) is None

def test_returns_none_when_message_has_no_json(self) -> None:
from agent_framework.exceptions import ToolExecutionException

inner = McpError(ErrorData(code=CONSENT_ERROR_CODE, message="no json here"))
inner = MCPError(code=CONSENT_ERROR_CODE, message="no json here")
exc = ToolExecutionException("MCP consent required", inner_exception=inner)
assert consent_url_from_error(exc) is None

Expand Down
2 changes: 1 addition & 1 deletion python/packages/hosting-mcp/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ classifiers = [
dependencies = [
"agent-framework-core>=1.13.0,<2",
"agent-framework-hosting==1.0.0a260730",
"mcp>=1.11.0,<2",
"mcp>=2.0.0,<3",
"pydantic>=2,<3",
Comment thread
PratikWayase marked this conversation as resolved.
]

Expand Down
Loading
Loading