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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,8 @@ CLAUDE.md

.env.claude/
.claude/

tests/**/.vscode/
tests/**/appPackage/
tests/**/infra/
tests/**/teamsapp*
13 changes: 11 additions & 2 deletions packages/ai/src/microsoft/teams/ai/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

from typing import Any, Awaitable, Callable

from microsoft.teams.ai.plugin import AIPluginProtocol

from .ai_model import AIModel
from .chat_prompt import ChatPrompt, ChatSendResult
from .function import Function
Expand All @@ -18,8 +20,15 @@ class Agent(ChatPrompt):
through the existence of the Agent.
"""

def __init__(self, model: AIModel, *, memory: Memory | None = None, functions: list[Function[Any]] | None = None):
super().__init__(model, functions=functions)
def __init__(
self,
model: AIModel,
*,
memory: Memory | None = None,
functions: list[Function[Any]] | None = None,
plugins: list[AIPluginProtocol] | None = None,
):
super().__init__(model, functions=functions, plugins=plugins)
self.memory = memory or ListMemory()

async def send(
Expand Down
38 changes: 17 additions & 21 deletions packages/ai/src/microsoft/teams/ai/chat_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import inspect
from dataclasses import dataclass
from inspect import isawaitable
from typing import Any, Awaitable, Callable, TypeVar
from typing import Any, Awaitable, Callable, Self, TypeVar

from pydantic import BaseModel

Expand Down Expand Up @@ -36,11 +36,11 @@ def __init__(
self.functions: dict[str, Function[Any]] = {func.name: func for func in functions} if functions else {}
self.plugins: list[AIPluginProtocol] = plugins or []

def with_function(self, function: Function[T]) -> "ChatPrompt":
def with_function(self, function: Function[T]) -> Self:
self.functions[function.name] = function
return self

def with_plugin(self, plugin: AIPluginProtocol) -> "ChatPrompt":
def with_plugin(self, plugin: AIPluginProtocol) -> Self:
"""Add a plugin to the chat prompt."""
self.plugins.append(plugin)
return self
Expand Down Expand Up @@ -122,25 +122,21 @@ async def _run_build_system_message_hooks(self, system_message: SystemMessage |
return current_system_message

async def _build_wrapped_functions(self) -> dict[str, Function[BaseModel]] | None:
wrapped_functions: dict[str, Function[BaseModel]] | None = None
if self.functions:
wrapped_functions = {}
for name, func in self.functions.items():
wrapped_functions[name] = Function[BaseModel](
name=func.name,
description=func.description,
parameter_schema=func.parameter_schema,
handler=self._wrap_function_handler(func.handler, name),
)

if wrapped_functions:
functions_list = list(wrapped_functions.values())
for plugin in self.plugins:
plugin_result = await plugin.on_build_functions(functions_list)
if plugin_result is not None:
functions_list = plugin_result
functions_list = list(self.functions.values()) if self.functions else []
for plugin in self.plugins:
plugin_result = await plugin.on_build_functions(functions_list)
if plugin_result is not None:
functions_list = plugin_result

wrapped_functions = {func.name: func for func in functions_list}
wrapped_functions: dict[str, Function[BaseModel]] | None = None
wrapped_functions = {}
for func in functions_list:
wrapped_functions[func.name] = Function[BaseModel](
name=func.name,
description=func.description,
parameter_schema=func.parameter_schema,
handler=self._wrap_function_handler(func.handler, func.name),
)

return wrapped_functions

Expand Down
4 changes: 2 additions & 2 deletions packages/ai/src/microsoft/teams/ai/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""

from dataclasses import dataclass
from typing import Any, Awaitable, Generic, Protocol, TypeVar, Union
from typing import Any, Awaitable, Dict, Generic, Protocol, TypeVar, Union

from pydantic import BaseModel

Expand All @@ -19,7 +19,7 @@ def __call__(self, params: Params) -> Union[str, Awaitable[str]]: ...
class Function(Generic[Params]):
name: str
description: str
parameter_schema: type[Params]
parameter_schema: Union[type[Params], Dict[str, Any]]
handler: FunctionHandler[Params]


Expand Down
10 changes: 5 additions & 5 deletions packages/apps/src/microsoft/teams/apps/app_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
ConversationReference,
GetUserTokenParams,
InvokeResponse,
JsonWebToken,
SentActivity,
TokenProtocol,
is_invoke_response,
Expand Down Expand Up @@ -97,18 +96,19 @@ async def _build_context(

# Check if user is signed in
is_signed_in = False
user_token: Optional[TokenProtocol] = None
user_token: Optional[str] = None
try:
user_token_res = await api_client.users.token.get(
GetUserTokenParams(
connection_name=self.default_connection_name or "default",
connection_name=self.default_connection_name,
user_id=activity.from_.id,
channel_id=activity.channel_id,
)
)
user_token = JsonWebToken(user_token_res.token)
user_token = user_token_res.token
is_signed_in = True
except Exception:
except Exception as e:
self.logger.debug(f"No token available {e}")
# User token not available
pass

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
CreateConversationParams,
GetBotSignInResourceParams,
GetUserTokenParams,
JsonWebToken,
MessageActivityInput,
SentActivity,
SignOutUserParams,
Expand Down Expand Up @@ -65,6 +66,7 @@ class SignInOptions:

oauth_card_text: str = "Please Sign In..."
sign_in_button_text: str = "Sign In"
connection_name: Optional[str] = None
override_sign_in_activity: Optional[
Callable[
[
Expand All @@ -90,7 +92,7 @@ def __init__(
logger: Logger,
storage: Storage[str, Any],
api: ApiClient,
user_token: Optional[TokenProtocol],
user_token: Optional[str],
conversation_ref: ConversationReference,
is_signed_in: bool,
connection_name: str,
Expand Down Expand Up @@ -135,7 +137,8 @@ def user_graph(self) -> "GraphServiceClient":

if self._user_graph is None:
try:
self._user_graph = _get_graph_client(self.user_token)
user_token = JsonWebToken(self.user_token)
self._user_graph = _get_graph_client(user_token)
except Exception as e:
self.logger.error(f"Failed to create user graph client: {e}")
raise RuntimeError(f"Failed to create user graph client: {e}") from e
Expand Down Expand Up @@ -247,13 +250,13 @@ async def sign_in(self, options: Optional[SignInOptions] = None) -> Optional[str
signin_opts = options or DEFAULT_SIGNIN_OPTIONS
oauth_card_text = signin_opts.oauth_card_text
sign_in_button_text = signin_opts.sign_in_button_text

connection_name = signin_opts.connection_name or self.connection_name
try:
# Try to get existing token
token_params = GetUserTokenParams(
channel_id=self.activity.channel_id,
user_id=self.activity.from_.id,
connection_name=self.connection_name,
connection_name=connection_name,
)
res = await self.api.users.token.get(token_params)
return res.token
Expand All @@ -263,7 +266,7 @@ async def sign_in(self, options: Optional[SignInOptions] = None) -> Optional[str

# Create token exchange state
token_exchange_state = TokenExchangeState(
connection_name=self.connection_name,
connection_name=connection_name,
conversation=self.conversation_ref,
relates_to=self.activity.relates_to,
ms_app_id=self.app_id,
Expand Down Expand Up @@ -297,7 +300,7 @@ async def sign_in(self, options: Optional[SignInOptions] = None) -> Optional[str
attachment=OAuthCardAttachment(
content=OAuthCard(
text=oauth_card_text,
connection_name=self.connection_name,
connection_name=connection_name,
token_exchange_resource=resource.token_exchange_resource,
token_post_resource=resource.token_post_resource,
buttons=[
Expand Down
Empty file added packages/mcp/README.md
Empty file.
23 changes: 23 additions & 0 deletions packages/mcp/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[project]
name = "microsoft.teams.mcp"
version = "0.0.1-alpha.4"
description = "library for handling mcp with teams ai library"
authors = [{ name = "Microsoft", email = "[email protected]" }]
readme = "README.md"
requires-python = ">=3.12"
repository = "https://github.com/microsoft/teams.py"
keywords = ["microsoft", "teams", "ai", "bot", "agents"]
license = "MIT"
dependencies = [
"mcp>=1.13.1",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/microsoft"]

[tool.hatch.build.targets.sdist]
include = ["src"]
8 changes: 8 additions & 0 deletions packages/mcp/src/microsoft/teams/mcp/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License.
"""

from .ai_plugin import McpClientPlugin, McpClientPluginParams, McpToolDetails

__all__ = ["McpClientPlugin", "McpClientPluginParams", "McpToolDetails"]
Loading
Loading