Adding factory methods for app-based graph clients - #481
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors the Teams hosting layer’s Microsoft Graph integration by centralizing Graph client creation and expanding the public APIs to acquire both user-authenticated and app-authenticated Graph clients, including connection selection and configurable Graph base URLs.
Changes:
- Introduces new
_graph.pyhelpers/providers to build Graph clients for user tokens vs application tokens. - Exposes new Graph acquisition methods on
TeamsAgentExtensionandTeamsTurnContextfor app-authenticated Graph clients (including per-connection selection). - Updates the core connections interface so token providers can be resolved with an optional
service_url.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_turn_context.py | Adds turn-context-level Graph client helpers (user + app) and wires them to shared _graph helpers. |
| libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_agent_extension.py | Adds extension-level app Graph client helpers and refactors existing user Graph client acquisition to the new helper. |
| libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_utils.py | Adds a new app Graph helper stub (currently problematic as implemented). |
| libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py | Introduces new authentication providers and common helper functions for creating user/app Graph clients. |
| libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/connections.py | Updates get_token_provider signature to accept an optional service_url. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (8)
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_turn_context.py:24
- Unused import: AccessTokenProviderBase is imported but never referenced in this module. Keeping it will trigger flake8 unused-import warnings.
from microsoft_agents.hosting.core import (
AccessTokenProviderBase,
AgentApplication,
TurnContext,
)
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_turn_context.py:142
- Parameter name typo:
hanlder_nameshould behandler_name(and forwarded consistently). As-is, it’s easy to call this API incorrectly and it leaks a misspelling into the public surface.
def get_graph_client(self, hanlder_name: str | None = None) -> GraphServiceClient:
"""
Get a Graph client for the current turn context.
:return: A :class:`GraphServiceClient` that authenticates each request via
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_turn_context.py:156
- Docstring mismatch:
get_app_graph_clientdocuments aconnection_nameparameter that doesn’t exist in the signature.
"""
Get a Graph client for the current turn context.
:param connection_name: Optional connection name to select a specific token provider.
:param graph_base_url: The base URL for the Microsoft Graph API.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_utils.py:155
- This helper stub introduces runtime errors:
_utils.pyhas nofrom __future__ import annotations, andAgentApplication/GraphServiceClientare not imported, so defining this function will raiseNameErrorat import time. It’s also unused in the package.
def _get_app_graph_client(
app: AgentApplication,
context: TurnContext,
graph_base_url: str,
connection_name: str | None = None,
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_agent_extension.py:324
- Docstring mismatch:
get_app_graph_clientdocuments aconnection_nameparameter that isn’t in the function signature.
"""Get the Graph Service client for the agent application.
:param context: The turn context.
:param connection_name: The name of the connection to use for authentication.
:param graph_base_url: The base URL for the Microsoft Graph API.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_agent_extension.py:338
- Docstring mismatch:
get_app_graph_client_for_connectiondoes not take acontextparameter, but the docstring claims it does.
"""Get the Graph Service client for the agent application using a specific connection.
:param context: The turn context.
:param connection_name: The name of the connection to use for authentication.
:param graph_base_url: The base URL for the Microsoft Graph API.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:48
- Docstring mismatch: this initializer now takes
auth: Authorization, but the docstring still refers to anappparameter.
"""Capture the context needed to resolve a token at request time.
:param app: The agent application whose authorization issues tokens.
:param context: The current turn context.
:param handler_name: The auth handler name used to acquire the token.
"""
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:145
graph_base_urlisn’t applied to the request adapter/client, so changing it likely won’t change where requests are sent (it only affects token scopes). Also, invalid URLs (missing scheme/host) will currently produce an invalid resource URL/scopes.
def _create_app_graph_service_client(
token_provider: AccessTokenProviderBase,
graph_base_url: str,
) -> GraphServiceClient:
"""Create a Graph client authenticated for the agent application.
:param app: The agent application whose authorization issues tokens.
:param handler_name: Optional auth handler name used to acquire the token.
:return: A :class:`GraphServiceClient` that authenticates each request via
the agent's authorization.
"""
url_parsed = urlparse(graph_base_url)
resource_url = f"{url_parsed.scheme}://{url_parsed.netloc}"
scopes = [f"{resource_url}/.default"]
return GraphServiceClient(
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (6)
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_turn_context.py:137
- The public API parameter name is misspelled as
hanlder_name, which is easy to miss and breaks keyword-argument callers expectinghandler_name. Rename the parameter and the internal reference for consistency with TeamsAgentExtension and the underlying helper.
def get_graph_client(self, hanlder_name: str | None = None) -> GraphServiceClient:
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_turn_context.py:154
- This docstring mentions a
connection_nameparameter that doesn't exist onget_app_graph_client, which is misleading for users. Remove the stale parameter documentation.
:param connection_name: Optional connection name to select a specific token provider.
:param graph_base_url: The base URL for the Microsoft Graph API.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_agent_extension.py:324
- This docstring mentions a
connection_nameparameter, butget_app_graph_clientonly acceptscontextandgraph_base_url. Remove the stale parameter documentation to avoid confusion.
:param connection_name: The name of the connection to use for authentication.
:param graph_base_url: The base URL for the Microsoft Graph API.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_agent_extension.py:337
- The docstring lists a
contextparameter that the method does not accept. Remove the incorrect:param context:line so the doc matches the API.
:param context: The turn context.
:param connection_name: The name of the connection to use for authentication.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:162
- This docstring documents a
connection_nameparameter that the function doesn't accept. Remove the stale parameter documentation so callers aren't misled.
:param connection_name: Optional connection name to select a specific token provider.
:param graph_base_url: The base URL for the Graph API.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:46
- The
__init__docstring refers to anappparameter, but the constructor now takesauth. Update the docstring so it matches the actual signature.
:param context: The current turn context.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (13)
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_turn_context.py:137
- The parameter name
hanlder_nameis misspelled. This breaks callers that use the public keyword argumenthandler_name=...and makes the API harder to discover.
def get_graph_client(self, hanlder_name: str | None = None) -> GraphServiceClient:
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_turn_context.py:144
- After renaming the parameter to
handler_name, update the call site to pass the corrected variable name (otherwise this will raise a NameError).
return _create_user_graph_service_client(self._app, self, hanlder_name)
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_turn_context.py:154
get_app_graph_clientdoes not acceptconnection_name, but the docstring lists it as a parameter. This is misleading for SDK consumers.
:param connection_name: Optional connection name to select a specific token provider.
:param graph_base_url: The base URL for the Microsoft Graph API.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_agent_extension.py:324
get_app_graph_clientdoes not acceptconnection_name, but the docstring documents it. Update the docstring to match the actual signature.
:param context: The turn context.
:param connection_name: The name of the connection to use for authentication.
:param graph_base_url: The base URL for the Microsoft Graph API.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_agent_extension.py:338
get_app_graph_client_for_connectiondoes not take acontextparameter, but the docstring lists one. This can confuse users and IDE signature help.
:param context: The turn context.
:param connection_name: The name of the connection to use for authentication.
:param graph_base_url: The base URL for the Microsoft Graph API.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:140
- The
_create_app_graph_service_clientdocstring still describesapp/handler_name, but the function now takestoken_providerandgraph_base_url. This mismatch makes the helper hard to use correctly.
:param token_provider: The access token provider for the agent application.
:param graph_base_url: The base URL for the Graph API.
:return: A :class:`GraphServiceClient` that authenticates each request via
the token provider.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:143
graph_base_urlis user-provided (via public APIs) but is not validated. If it is missing a scheme/host,resource_urlbecomes invalid (e.g. "://") and token acquisition will fail with a confusing error.
url_parsed = urlparse(graph_base_url)
resource_url = f"{url_parsed.scheme}://{url_parsed.netloc}"
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:155
- Use the module-level
_DEFAULT_GRAPH_BASE_URLconstant for the default parameter value to avoid drift from the exported default used elsewhere.
graph_base_url: str = "https://graph.microsoft.com/v1.0",
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:177
- Use the module-level
_DEFAULT_GRAPH_BASE_URLconstant for the default parameter value to avoid multiple hard-coded defaults.
) -> GraphServiceClient:
test_samples/hosting_msteams/graph-clients/pyproject.toml:8
- This sample's project metadata appears to be copied from the conversation-agent sample (
nameanddescription). Update it so packaging/installation and docs reflect the actual graph-clients sample.
name = "graph-clients"
version = "0.1.0"
description = "Teams Graph clients sample — demonstrates app-only and delegated Microsoft Graph clients"
test_samples/hosting_msteams/graph-clients/src/agent.py:23
- Avoid calling
logging.basicConfig(...)at import time in samples; it configures global logging and can override host applications. Other Teams samples in this repo no longer callbasicConfigand rely on env-based logging configuration instead.
logger = logging.getLogger(__name__)
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:46
- The
_SDKUserAuthenticationProvider.__init__docstring still documents a:param app:even though the constructor takesauth. This is misleading when browsing docs/IntelliSense.
:param context: The current turn context.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:162
_common_get_app_graph_clientdoes not accept aconnection_name, but the docstring documents one. This can mislead maintainers and readers of generated docs.
:param app: The agent application whose authorization issues tokens.
:param context: The current turn context.
:param graph_base_url: The base URL for the Graph API.
:return: A :class:`GraphServiceClient` that authenticates each request via
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (8)
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_turn_context.py:137
- Parameter name
hanlder_namelooks misspelled. This is part of a public-ish API surface (callers may use keyword arguments), so the typo can break expected usage and is easy to propagate.
def get_graph_client(self, hanlder_name: str | None = None) -> GraphServiceClient:
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_turn_context.py:155
- Docstring references a
connection_nameparameter, butget_app_graph_clientdoesn’t accept one. This can mislead users into thinking connection selection is supported on this overload.
:param connection_name: Optional connection name to select a specific token provider.
:param graph_base_url: The base URL for the Microsoft Graph API.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_agent_extension.py:324
- Docstring lists a
connection_nameparameter forget_app_graph_client, but the method signature has onlycontextandgraph_base_url. This is misleading for API consumers.
:param connection_name: The name of the connection to use for authentication.
:param graph_base_url: The base URL for the Microsoft Graph API.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_agent_extension.py:338
- Docstring references a
contextparameter, butget_app_graph_client_for_connectiondoesn’t accept one. Either add the parameter for consistency with other extension helpers, or update the docstring.
:param context: The turn context.
:param connection_name: The name of the connection to use for authentication.
:param graph_base_url: The base URL for the Microsoft Graph API.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:47
_SDKUserAuthenticationProvider.__init__docstring still documents anappparameter, but the constructor now takesauth. This makes the internal API docs inaccurate and harder to follow.
:param app: The agent application whose authorization issues tokens.
:param context: The current turn context.
:param handler_name: The auth handler name used to acquire the token.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:145
_create_app_graph_service_clientassumesgraph_base_urlis a fully-qualified URL. If a caller passes a relative URL (or a string without scheme/host),resource_urlbecomes invalid (e.g., "://") and the computed/.defaultscope will be wrong.
url_parsed = urlparse(graph_base_url)
resource_url = f"{url_parsed.scheme}://{url_parsed.netloc}"
scopes = [f"{resource_url}/.default"]
request_adapter = GraphRequestAdapter(
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:156
- These helpers already define
_DEFAULT_GRAPH_BASE_URL, but the default argument uses a duplicated string literal. Using the shared constant prevents accidental drift if the default ever changes.
graph_base_url: str = "https://graph.microsoft.com/v1.0",
) -> GraphServiceClient:
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:177
- Same as above: use
_DEFAULT_GRAPH_BASE_URLrather than repeating the literal default base URL in the function signature.
graph_base_url: str = "https://graph.microsoft.com/v1.0",
) -> GraphServiceClient:
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:145
graph_base_urlis parsed to computeresource_url/scopes, but invalid values (e.g. missing scheme/host) will silently produce malformed scopes like:///.defaultand later auth failures. Validate the parsed URL up front and raise a clearValueErrorwhen it’s not an absolute http(s) URL.
url_parsed = urlparse(graph_base_url)
resource_url = f"{url_parsed.scheme}://{url_parsed.netloc}"
scopes = [f"{resource_url}/.default"]
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:185
- The
_common_get_app_graph_client_for_connectiondocstring references the agent's authorization issuing tokens, but the implementation uses the connection manager's default/named connection token provider. Update the docstring to avoid implying Authorization is involved here.
:param app: The agent application whose authorization issues tokens.
:param connection_name: Optional connection name to select a specific token provider.
:param graph_base_url: The base URL for the Graph API.
:return: A :class:`GraphServiceClient` that authenticates each request via
the token provider from the connection.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_turn_context.py:184
get_app_graph_client_for_connectionbuilds an app-only client using the named connection and does not use the turn context's Authorization. The docstring currently implies it is for the current turn context and authenticates via the agent's authorization. Update it to describe app-only auth via the specified connection token provider.
"""
Get a Graph client for the current turn context using a specific connection.
:param connection_name: The name of the connection to use for authentication.
:param graph_base_url: The base URL for the Microsoft Graph API.
:return: A :class:`GraphServiceClient` that authenticates each request via
the agent's authorization.
"""
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:146
- _create_app_graph_service_client() assumes graph_base_url is an absolute URL. If a caller passes something like "graph.microsoft.com/v1.0" (no scheme), urlparse() yields an empty scheme/netloc and this code will request a token for resource_url='://', producing confusing auth failures. Validate scheme+host early and raise a clear ValueError.
url_parsed = urlparse(graph_base_url)
resource_url = f"{url_parsed.scheme}://{url_parsed.netloc}"
scopes = [f"{resource_url}/.default"]
request_adapter = GraphRequestAdapter(
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:160
- Docstring for _common_get_app_graph_client says the app's authorization issues tokens, but this helper actually uses app.connection_manager.get_token_provider() to get an app-only token provider. Updating the parameter description avoids misleading API docs.
:param app: The agent application whose authorization issues tokens.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:181
- Docstring for _common_get_app_graph_client_for_connection says the app's authorization issues tokens, but this helper actually uses the connection manager to resolve an app-only token provider. Please align the docstring with the implementation.
:param app: The agent application whose authorization issues tokens.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:170
- _common_get_app_graph_client assumes context.activity.service_url is present, but Activity.service_url is optional in the schema and ConnectionManager.get_token_provider currently raises if service_url is falsy. Guard against a missing/empty service_url here so callers get a clear, local error instead of a downstream failure.
if not context.identity:
raise ValueError("TurnContext.identity is required to get a Graph client.")
token_provider = app.connection_manager.get_token_provider(
context.identity, context.activity.service_url
)
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:170
- The PR description says get_token_provider was modified to accept an optional service_url, but the current Connections/ConnectionManager APIs still require a non-empty service_url and raise otherwise. Either update the PR description to reflect the existing contract, or implement optional service_url handling in the core authorization interfaces/implementations.
token_provider = app.connection_manager.get_token_provider(
context.identity, context.activity.service_url
)
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:12
- The module docstring focuses only on delegated (Authorization-based) Graph clients, but this module now also creates app-only Graph clients via AccessTokenProviderBase/connection manager. Update the docstring so it reflects both supported authentication paths.
"""
from typing import Any
from urllib.parse import urlparse
2003bc0
into
main
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/_graph.py:123
- The
_create_user_graph_service_clientdocstring says the client authenticates via the agent's connections, but this helper usesapp.auth(Authorization) to acquire user-scoped tokens. This mismatch can confuse callers about which auth flow is used.
:return: A :class:`GraphServiceClient` that authenticates each request via
the agent's connections.
libraries/microsoft-agents-hosting-msteams/microsoft_agents/hosting/msteams/teams_agent_extension.py:316
- The
handler_nameparameter is typed as optional (str | None), but the docstring describes it as required. Align the docstring with the signature to avoid confusing users of the API.
:param context: The turn context.
:param handler_name: The name of the handler.
:param graph_base_url: The base URL for the Microsoft Graph API.
| if not context.identity: | ||
| raise ValueError("TurnContext.identity is required to get a Graph client.") | ||
| token_provider = app.connection_manager.get_token_provider( | ||
| context.identity, context.activity.service_url | ||
| ) | ||
| return _create_app_graph_service_client(token_provider, graph_base_url) |
This pull request refactors and extends the Microsoft Graph client integration in the Teams hosting package. It introduces new helper functions and classes for acquiring Graph clients with both user and application authentication, adds support for selecting connections and base URLs, and exposes these capabilities through the main extension and turn context APIs. The changes improve flexibility, separation of concerns, and ease of use when working with Microsoft Graph from agent code.
Microsoft Graph client acquisition and authentication improvements:
_create_user_graph_service_client,_create_app_graph_service_client,_common_get_app_graph_client,_common_get_app_graph_client_for_connection) to centralize and simplify the creation of Microsoft Graph clients with user or application authentication, supporting custom base URLs and connection selection. [1] [2]_SDKUserAuthenticationProviderfor user-scoped tokens, and_SDKAuthenticationProviderfor application-scoped tokens, separating their responsibilities and improving clarity. [1] [2] [3]API enhancements for Teams extension and turn context:
get_app_graph_clientandget_app_graph_client_for_connectionin bothTeamsAgentExtensionandTeamsTurnContext, enabling easy access to Graph clients with application authentication and support for custom connections and base URLs. [1] [2]Core authorization interface improvement:
get_token_providermethod in the core authorization interface to accept an optionalservice_url, improving flexibility for token acquisition scenarios.These changes collectively make it easier and more robust for agent developers to acquire and use Microsoft Graph clients with the correct authentication context for both user and application scenarios.