From 714923418c0decf2ae6b5e629682ab8320859146 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Fri, 2 Oct 2026 11:09:37 -0700 Subject: [PATCH 1/4] Ensuring headers get set across rest clients --- .../core/connector/client/connector_client.py | 7 +- .../connector/client/user_token_client.py | 6 +- .../connector/mcs/mcs_connector_client.py | 8 +++ .../connector/test_connector_client.py | 70 +++++++++++++++++++ .../connector/test_mcs_connector_client.py | 55 +++++++++++++++ .../connector/test_user_token_client.py | 35 ++++++++++ 6 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 tests/hosting_core/connector/test_mcs_connector_client.py diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py index 0844c578a..3a67335a1 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py @@ -789,10 +789,9 @@ def __init__( } # Create session with the base URL - session = session or ClientSession( - base_url=endpoint, - headers=headers, - ) + session = session or ClientSession(base_url=endpoint) + session.headers.update(headers) + logger.debug( "ConnectorClient initialized with endpoint: %s and headers: %s", endpoint, diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py index 0a00543c0..7ddae2c3e 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py @@ -65,10 +65,8 @@ def __init__( } # Create session with the base URL - session = session or ClientSession( - base_url=endpoint, - headers=headers, - ) + session = session or ClientSession(base_url=endpoint) + session.headers.update(headers) logger.debug( "Creating UserTokenClient with endpoint: %s and headers: %s", endpoint, diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/mcs/mcs_connector_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/mcs/mcs_connector_client.py index 707bc5c29..ba9eba2bc 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/mcs/mcs_connector_client.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/mcs/mcs_connector_client.py @@ -14,6 +14,7 @@ from ..conversations_base import ConversationsBase from ..client._base_client import _BaseClient from .._utils import _handle_request_error +from ..get_product_info import get_product_info logger = logging.getLogger(__name__) @@ -224,6 +225,13 @@ def __init__(self, endpoint: str, client: Optional[ClientSession] = None): self._endpoint = endpoint self._client = client or ClientSession() + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": get_product_info(), + } + self._client.headers.update(headers) + self._conversations = MCSConversations(self._client, self._endpoint) self._attachments = MCSAttachments() diff --git a/tests/hosting_core/connector/test_connector_client.py b/tests/hosting_core/connector/test_connector_client.py index ab7a03095..9ca0fae58 100644 --- a/tests/hosting_core/connector/test_connector_client.py +++ b/tests/hosting_core/connector/test_connector_client.py @@ -21,6 +21,8 @@ ConnectorClient, ConversationsOperations, ) +from microsoft_agents.hosting.core.connector.get_product_info import get_product_info +from microsoft_agents.hosting.core.connector.teams import TeamsConnectorClient from microsoft_agents.hosting.core.header_propagation import HeaderPropagationContext @@ -945,3 +947,71 @@ async def handler(request): assert "conv_sub_id" in captured["raw_path"] finally: await server.close() + + +class TestUserAgentHeader: + """Ensures ConnectorClient and TeamsConnectorClient send a User-Agent header.""" + + @pytest.mark.asyncio + async def test_connector_client_sets_user_agent_header(self): + client = ConnectorClient(endpoint="https://example.org/", token="token") + try: + assert client.client.headers["User-Agent"] == get_product_info() + finally: + await client.client.close() + + @pytest.mark.asyncio + async def test_connector_client_sends_user_agent_header_on_request(self): + captured = {} + + async def handler(request): + captured["user_agent"] = request.headers.get("User-Agent") + return web.json_response({"id": "activity-id-123"}) + + routes = [web.post("/v3/conversations/{conversation_id}/activities", handler)] + app = _create_app(routes) + + server = TestServer(app) + await server.start_server() + try: + client = ConnectorClient(endpoint=str(server.make_url("/")), token="token") + await client.conversations.send_to_conversation( + "conv-1", Activity(type="message", text="hi") + ) + finally: + await server.close() + await client.client.close() + + assert captured["user_agent"] == get_product_info() + + @pytest.mark.asyncio + async def test_teams_connector_client_sets_user_agent_header(self): + client = TeamsConnectorClient(endpoint="https://example.org/", token="token") + try: + assert client.client.headers["User-Agent"] == get_product_info() + finally: + await client.client.close() + + @pytest.mark.asyncio + async def test_teams_connector_client_sends_user_agent_header_on_request(self): + captured = {} + + async def handler(request): + captured["user_agent"] = request.headers.get("User-Agent") + return web.json_response({"id": "team-1"}) + + routes = [web.get("/v3/teams/{team_id}", handler)] + app = _create_app(routes) + + server = TestServer(app) + await server.start_server() + try: + client = TeamsConnectorClient( + endpoint=str(server.make_url("/")), token="token" + ) + await client.fetch_team_details("team-1") + finally: + await server.close() + await client.client.close() + + assert captured["user_agent"] == get_product_info() diff --git a/tests/hosting_core/connector/test_mcs_connector_client.py b/tests/hosting_core/connector/test_mcs_connector_client.py new file mode 100644 index 000000000..efeada7ab --- /dev/null +++ b/tests/hosting_core/connector/test_mcs_connector_client.py @@ -0,0 +1,55 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +"""Tests for MCSConnectorClient User-Agent header behavior.""" + +import pytest +from aiohttp import web +from aiohttp.test_utils import TestServer + +from microsoft_agents.activity import Activity +from microsoft_agents.hosting.core.connector.get_product_info import get_product_info +from microsoft_agents.hosting.core.connector.mcs import MCSConnectorClient + + +class TestMCSConnectorClientUserAgentHeader: + """Ensures MCSConnectorClient sends a User-Agent header. + + NOTE: Unlike ConnectorClient/TeamsConnectorClient and UserTokenClient, + MCSConnectorClient does not currently set a custom "agents-sdk-py" User-Agent + header on its session or outgoing requests, so these tests currently fail. + """ + + @pytest.mark.asyncio + async def test_sets_user_agent_header(self): + client = MCSConnectorClient(endpoint="https://example.org/endpoint") + try: + assert client._client.headers.get("User-Agent") == get_product_info() + finally: + await client.close() + + @pytest.mark.asyncio + async def test_sends_user_agent_header_on_request(self): + captured = {} + + async def handler(request): + captured["user_agent"] = request.headers.get("User-Agent") + return web.json_response({"id": "activity-id-123"}) + + app = web.Application() + app.router.add_post("/endpoint", handler) + server = TestServer(app) + await server.start_server() + try: + endpoint = str(server.make_url("/endpoint")) + client = MCSConnectorClient(endpoint=endpoint) + try: + await client.conversations.send_to_conversation( + "conv-1", Activity(type="message", text="hi") + ) + finally: + await client.close() + finally: + await server.close() + + assert captured["user_agent"] == get_product_info() diff --git a/tests/hosting_core/connector/test_user_token_client.py b/tests/hosting_core/connector/test_user_token_client.py index ff08f7faf..9de278ded 100644 --- a/tests/hosting_core/connector/test_user_token_client.py +++ b/tests/hosting_core/connector/test_user_token_client.py @@ -21,6 +21,7 @@ UserToken, UserTokenClient, ) +from microsoft_agents.hosting.core.connector.get_product_info import get_product_info from microsoft_agents.hosting.core.header_propagation import HeaderPropagationContext @@ -407,3 +408,37 @@ async def handler(request): await server.close() assert exc_info.value.status == 500 + + +class TestUserTokenClientUserAgentHeader: + """Ensures UserTokenClient sends a User-Agent header.""" + + @pytest.mark.asyncio + async def test_sets_user_agent_header(self): + client = UserTokenClient("https://example.org/", token="", app_id="app-id") + try: + assert client.client.headers["User-Agent"] == get_product_info() + finally: + await client.close() + + @pytest.mark.asyncio + async def test_sends_user_agent_header_on_request(self): + captured = {} + + async def handler(request): + captured["user_agent"] = request.headers.get("User-Agent") + return web.json_response({"token": "token"}) + + app = web.Application() + app.router.add_get("/api/usertoken/GetToken", handler) + server = TestServer(app) + await server.start_server() + + client = UserTokenClient(str(server.make_url("/")), token="", app_id="app-id") + try: + await client.get_user_token("user", "connection", "msteams") + finally: + await client.close() + await server.close() + + assert captured["user_agent"] == get_product_info() From 921143eb52ea7a74ecffac8f200d22e358d79c47 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Mon, 5 Oct 2026 11:49:34 -0700 Subject: [PATCH 2/4] Allowing user-specified User-Agent header --- .../hosting/core/connector/client/connector_client.py | 8 ++++---- .../hosting/core/connector/client/user_token_client.py | 8 ++++---- .../hosting/core/connector/mcs/mcs_connector_client.py | 2 +- tests/hosting_core/connector/test_connector_client.py | 4 +++- tests/hosting_core/connector/test_mcs_connector_client.py | 7 +------ 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py index 3a67335a1..be5f73e6e 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py @@ -781,15 +781,15 @@ def __init__( if not endpoint.endswith("/"): endpoint += "/" + # Create session with the base URL + session = session or ClientSession(base_url=endpoint) + # Configure headers with JSON acceptance headers = { "Accept": "application/json", "Content-Type": "application/json", - "User-Agent": get_product_info(), + "User-Agent": session.headers.get("User-Agent", get_product_info()), } - - # Create session with the base URL - session = session or ClientSession(base_url=endpoint) session.headers.update(headers) logger.debug( diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py index 7ddae2c3e..235221d25 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py @@ -57,15 +57,15 @@ def __init__( if not endpoint.endswith("/"): endpoint += "/" + # Create session with the base URL + session = session or ClientSession(base_url=endpoint) + # Configure headers with JSON acceptance headers = { "Accept": "application/json", "Content-Type": "application/json", - "User-Agent": get_product_info(), + "User-Agent": session.headers.get("User-Agent", get_product_info()), } - - # Create session with the base URL - session = session or ClientSession(base_url=endpoint) session.headers.update(headers) logger.debug( "Creating UserTokenClient with endpoint: %s and headers: %s", diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/mcs/mcs_connector_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/mcs/mcs_connector_client.py index ba9eba2bc..6f50087f3 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/mcs/mcs_connector_client.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/mcs/mcs_connector_client.py @@ -228,7 +228,7 @@ def __init__(self, endpoint: str, client: Optional[ClientSession] = None): headers = { "Accept": "application/json", "Content-Type": "application/json", - "User-Agent": get_product_info(), + "User-Agent": self._client.headers.get("User-Agent", get_product_info()), } self._client.headers.update(headers) diff --git a/tests/hosting_core/connector/test_connector_client.py b/tests/hosting_core/connector/test_connector_client.py index 9ca0fae58..dfd03f7fa 100644 --- a/tests/hosting_core/connector/test_connector_client.py +++ b/tests/hosting_core/connector/test_connector_client.py @@ -973,6 +973,7 @@ async def handler(request): server = TestServer(app) await server.start_server() + client = None try: client = ConnectorClient(endpoint=str(server.make_url("/")), token="token") await client.conversations.send_to_conversation( @@ -980,7 +981,8 @@ async def handler(request): ) finally: await server.close() - await client.client.close() + if client is not None: + await client.client.close() assert captured["user_agent"] == get_product_info() diff --git a/tests/hosting_core/connector/test_mcs_connector_client.py b/tests/hosting_core/connector/test_mcs_connector_client.py index efeada7ab..880a23169 100644 --- a/tests/hosting_core/connector/test_mcs_connector_client.py +++ b/tests/hosting_core/connector/test_mcs_connector_client.py @@ -13,12 +13,7 @@ class TestMCSConnectorClientUserAgentHeader: - """Ensures MCSConnectorClient sends a User-Agent header. - - NOTE: Unlike ConnectorClient/TeamsConnectorClient and UserTokenClient, - MCSConnectorClient does not currently set a custom "agents-sdk-py" User-Agent - header on its session or outgoing requests, so these tests currently fail. - """ + """Ensures MCSConnectorClient sends a User-Agent header.""" @pytest.mark.asyncio async def test_sets_user_agent_header(self): From ea842e779973f9f3aab3c51c404aa69845385b44 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Mon, 5 Oct 2026 12:20:01 -0700 Subject: [PATCH 3/4] Addressing PR feedback --- .../connector/test_connector_client.py | 16 ++++++++++------ .../connector/test_user_token_client.py | 11 +++++++++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/tests/hosting_core/connector/test_connector_client.py b/tests/hosting_core/connector/test_connector_client.py index dfd03f7fa..19e9fa59a 100644 --- a/tests/hosting_core/connector/test_connector_client.py +++ b/tests/hosting_core/connector/test_connector_client.py @@ -973,16 +973,18 @@ async def handler(request): server = TestServer(app) await server.start_server() - client = None + session = ClientSession(base_url=str(server.make_url("/"))) try: - client = ConnectorClient(endpoint=str(server.make_url("/")), token="token") + client = ConnectorClient( + endpoint=str(server.make_url("/")), token="token", session=session + ) + assert client.client is session await client.conversations.send_to_conversation( "conv-1", Activity(type="message", text="hi") ) finally: await server.close() - if client is not None: - await client.client.close() + await session.close() assert captured["user_agent"] == get_product_info() @@ -1007,13 +1009,15 @@ async def handler(request): server = TestServer(app) await server.start_server() + session = ClientSession(base_url=str(server.make_url("/"))) try: client = TeamsConnectorClient( - endpoint=str(server.make_url("/")), token="token" + endpoint=str(server.make_url("/")), token="token", session=session ) + assert client.client is session await client.fetch_team_details("team-1") finally: await server.close() - await client.client.close() + await session.close() assert captured["user_agent"] == get_product_info() diff --git a/tests/hosting_core/connector/test_user_token_client.py b/tests/hosting_core/connector/test_user_token_client.py index 9de278ded..d9ca7513d 100644 --- a/tests/hosting_core/connector/test_user_token_client.py +++ b/tests/hosting_core/connector/test_user_token_client.py @@ -434,11 +434,18 @@ async def handler(request): server = TestServer(app) await server.start_server() - client = UserTokenClient(str(server.make_url("/")), token="", app_id="app-id") + session = ClientSession(base_url=str(server.make_url("/"))) try: + client = UserTokenClient( + str(server.make_url("/")), + token="", + app_id="app-id", + session=session, + ) + assert client.client is session await client.get_user_token("user", "connection", "msteams") finally: - await client.close() + await session.close() await server.close() assert captured["user_agent"] == get_product_info() From ca6c050ca24fad3b09c1b61b710b774567e130d3 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Mon, 5 Oct 2026 12:21:54 -0700 Subject: [PATCH 4/4] updating changelog --- changelog.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/changelog.md b/changelog.md index f7ca38055..ff324578e 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,14 @@ +# Microsoft 365 Agents SDK for Python - Release Notes v1.9.0 (Unreleased) + +**Release Date:** Unreleased +**Previous Version:** 1.8.0 (Released 2026-10-01) + +## Bug Fixes + +- **Connector User-Agent Headers**: Added the SDK `User-Agent` header to `MCSConnectorClient` and ensured `ConnectorClient`, `TeamsConnectorClient`, and `UserTokenClient` apply it to caller-provided `ClientSession` instances. + +--- + # Microsoft 365 Agents SDK for Python - Release Notes v1.8.0 **Release Date:** 2026-10-01