From c916ee2f6331fee4c68ac40a5c9adbde02abb294 Mon Sep 17 00:00:00 2001 From: heyitsaamir Date: Fri, 12 Sep 2025 15:46:58 -0700 Subject: [PATCH 1/2] MCP test updates and feedback loop updates --- packages/apps/src/microsoft/teams/apps/app.py | 6 +- .../src/handlers/feedback_management.py | 10 +- tests/mcp-client/README.md | 197 ++++++++++++++++++ tests/mcp-client/src/main.py | 152 ++++++++++++-- tests/mcp-server/README.md | 133 ++++++++++++ tests/mcp-server/src/main.py | 86 +++++++- 6 files changed, 561 insertions(+), 23 deletions(-) diff --git a/packages/apps/src/microsoft/teams/apps/app.py b/packages/apps/src/microsoft/teams/apps/app.py index efc0a274..39d9fb29 100644 --- a/packages/apps/src/microsoft/teams/apps/app.py +++ b/packages/apps/src/microsoft/teams/apps/app.py @@ -181,7 +181,9 @@ def router(self) -> ActivityRouter: @property def id(self) -> Optional[str]: """The app's ID from tokens.""" - return getattr(self._tokens.bot, "app_id", None) or getattr(self._tokens.graph, "app_id", None) + return ( + self._tokens.bot.app_id if self._tokens.bot else self._tokens.graph.app_id if self._tokens.graph else None + ) @property def name(self) -> Optional[str]: @@ -268,7 +270,7 @@ async def send(self, conversation_id: str, activity: str | ActivityParams | Adap """Send an activity proactively.""" if self.id is None or self.name is None: - raise ValueError("app not started") + raise ValueError(f"app not started {self.tokens.bot}") conversation_ref = ConversationReference( channel_id="msteams", diff --git a/tests/ai-test/src/handlers/feedback_management.py b/tests/ai-test/src/handlers/feedback_management.py index 2a62884a..8dfaaf77 100644 --- a/tests/ai-test/src/handlers/feedback_management.py +++ b/tests/ai-test/src/handlers/feedback_management.py @@ -49,12 +49,12 @@ async def handle_feedback_submission(ctx: ActivityContext[MessageSubmitActionInv return # Type-safe access to activity value - value_dict = activity.value.model_dump() if hasattr(activity.value, "model_dump") else {} - action_value: Dict[str, Any] = value_dict.get("actionValue", {}) - reaction: str | None = action_value.get("reaction") - feedback_str: str | None = action_value.get("feedback") - assert feedback_str, "No feedback string found in action_value" + invoke_value = activity.value + assert invoke_value.action_name == "feedback" + feedback_str = invoke_value.action_value.feedback + reaction = invoke_value.action_value.reaction feedback_json: Dict[str, Any] = json.loads(feedback_str) + # { 'feedbackText': 'the ai response was great!' } if not activity.reply_to_id: logger.warning(f"No replyToId found for messageId {activity.id}") diff --git a/tests/mcp-client/README.md b/tests/mcp-client/README.md index e69de29b..c69212fa 100644 --- a/tests/mcp-client/README.md +++ b/tests/mcp-client/README.md @@ -0,0 +1,197 @@ +# Sample: MCP Client + +A comprehensive test demonstrating MCP (Machine Conversation Protocol) client functionality using the Microsoft Teams Python SDK. This test shows how to integrate remote MCP servers into your Teams application, allowing AI agents to access external tools via the SSE protocol. + +## Prerequisites + +- Python 3.12 or later +- UV package manager +- An Microsoft 365 development account. If you don't have one, you can get one for free by signing up for the [Microsoft 365 Developer Program](https://developer.microsoft.com/microsoft-365/dev-program). + +## Setup + +1. Install dependencies: + +```bash +uv sync +``` + +2. Set up your `.env` file with your API keys: + +```bash +# Azure OpenAI (required) +AZURE_OPENAI_API_KEY= +AZURE_OPENAI_ENDPOINT= +AZURE_OPENAI_MODEL= +AZURE_OPENAI_API_VERSION= + +# Alternatively, set the OpenAI API key: +OPENAI_API_KEY= + +# GitHub PAT for MCP server (optional) +GITHUB_PAT= +``` + +## Run + +```bash +# Activate virtual environment +source .venv/bin/activate # On macOS/Linux +# .venv\Scripts\Activate # On Windows + +# Run the MCP client +python tests/mcp-client/src/main.py +``` + +## Features Demonstrated + +### Core MCP Client Functionality +- **Remote MCP Server Integration** - Connect to external MCP servers via SSE protocol +- **Multiple Server Support** - Demonstrate connecting to multiple MCP servers simultaneously +- **ChatPrompt Integration** - Direct integration with ChatPrompt for tool access +- **Agent Integration** - Stateful conversation with MCP tools through Agent pattern +- **DevTools Integration** - Monitor MCP requests and responses in real-time + +### Available Commands + +| Command | Description | Example Usage | +|---------|-------------|---------------| +| `agent ` | Use stateful Agent with MCP tools | `agent What's the weather like?` | +| `prompt ` | Use stateless ChatPrompt with MCP tools | `prompt Find information about Python` | +| `mcp info` | Show connected MCP servers and usage | `mcp info` | +| `` | Fallback to Agent with MCP tools | `Hello, can you help me?` | + +### Architecture Patterns + +#### 1. ChatPrompt with MCP (Stateless) +```python +chat_prompt = ChatPrompt( + completions_model, + plugins=[mcp_plugin] +) + +result = await chat_prompt.send( + input=query, + instructions="You are a helpful assistant with access to remote MCP tools." +) +``` + +#### 2. Agent with MCP (Stateful) +```python +responses_agent = Agent( + responses_model, + memory=chat_memory, + plugins=[mcp_plugin] +) + +result = await responses_agent.send(query) +``` + +#### 3. Multiple MCP Server Configuration +```python +mcp_plugin = McpClientPlugin() +mcp_plugin.use_mcp_server("https://learn.microsoft.com/api/mcp") +mcp_plugin.use_mcp_server("https://example.com/mcp/weather") +mcp_plugin.use_mcp_server("https://example.com/mcp/pokemon") +``` + +### Connected MCP Servers + +- **Microsoft Learn API** - `https://learn.microsoft.com/api/mcp` + - Provides access to Microsoft documentation and learning resources + - Demonstrates basic MCP server integration without authentication + +- **GitHub Copilot API** - `https://api.githubcopilot.com/mcp/` (Optional) + - Requires GitHub Personal Access Token (`GITHUB_PAT` environment variable) + - Demonstrates authenticated MCP server integration with headers + - Provides access to GitHub repositories, issues, and code analysis tools + +**Authentication Example:** +```python +from microsoft.teams.mcpplugin import McpClientPluginParams + +# This example uses a PersonalAccessToken, but you may get +# the user's oauth token as well by getting them to sign in +# and then using app.sign_in to get their token. +GITHUB_PAT = getenv("GITHUB_PAT") + +# GitHub MCP server with Bearer token authentication +if GITHUB_PAT: + mcp_plugin.use_mcp_server( + "https://api.githubcopilot.com/mcp/", + McpClientPluginParams(headers={ + "Authorization": f"Bearer {GITHUB_PAT}" + }) + ) +``` + +## How MCP Client Works + +1. **Server Discovery** - MCP Client connects to remote servers via SSE protocol +2. **Tool Loading** - Remote tools are loaded and made available to the LLM +3. **Function Integration** - Tools are treated like local functions by ChatPrompt/Agent +4. **Remote Execution** - When LLM calls a tool, request is sent to remote MCP server +5. **Result Processing** - Response from remote server is returned to LLM for use + +## Testing the MCP Client + +### 1. Start the Client +```bash +python tests/mcp-client/src/main.py +``` + +### 2. Test Different Patterns + +**Stateful Agent Pattern:** +``` +agent Tell me about Microsoft Teams development +``` + +**Stateless ChatPrompt Pattern:** +``` +prompt What are the latest features in .NET? +``` + +**General Chat (uses Agent by default):** +``` +Hello, I need help with Azure functions +``` + +**Get MCP Information:** +``` +mcp info +``` + +### 3. Monitor in DevTools +- Navigate to the DevTools interface +- View MCP requests and responses in real-time +- See which remote tools are being called by the LLM +- Monitor latency and response data from remote servers + +## Key Implementation Details + +- **SSE Protocol** - Uses Server-Sent Events for remote MCP server communication +- **Plugin Architecture** - MCP Client integrates as a plugin with ChatPrompt and Agent +- **Tool Discovery** - Remote tools are automatically discovered and made available +- **Error Handling** - Graceful handling of remote server connectivity issues +- **Memory Management** - Proper memory handling for stateful vs stateless patterns +- **Type Safety** - Full typing support with proper async/await patterns + +## Remote MCP Server Requirements + +For connecting to your own MCP servers, ensure they: + +1. **Support SSE Protocol** - Server must implement Server-Sent Events communication +2. **Follow MCP Specification** - Implement proper MCP protocol for tool definition and execution +3. **HTTPS Endpoint** - Server should be accessible via HTTPS URL +4. **Authentication** - If required, proper header-based authentication support + +## Use Cases Demonstrated + +- **Human-in-the-loop** - Remote tools can ask for user confirmation +- **External APIs** - Access to third-party services via MCP protocol +- **Distributed Architecture** - Tools can be hosted separately from main application +- **Tool Sharing** - Multiple applications can use the same MCP servers +- **Scalability** - Remote tools can be scaled independently + +This implementation provides a complete foundation for integrating external MCP servers into Teams applications, enabling powerful distributed AI agent architectures. \ No newline at end of file diff --git a/tests/mcp-client/src/main.py b/tests/mcp-client/src/main.py index 4dd67e57..ec6989ba 100644 --- a/tests/mcp-client/src/main.py +++ b/tests/mcp-client/src/main.py @@ -4,34 +4,162 @@ """ import asyncio +import re +from os import getenv -from microsoft.teams.ai import Agent, ListMemory -from microsoft.teams.api import MessageActivity, TypingActivityInput +from dotenv import find_dotenv, load_dotenv +from microsoft.teams.ai import Agent, ChatPrompt, ListMemory +from microsoft.teams.api import MessageActivity, MessageActivityInput, TypingActivityInput from microsoft.teams.apps import ActivityContext, App from microsoft.teams.devtools import DevToolsPlugin -from microsoft.teams.mcpplugin import McpClientPlugin -from microsoft.teams.openai import OpenAIResponsesAIModel +from microsoft.teams.mcpplugin import McpClientPlugin, McpClientPluginParams +from microsoft.teams.openai import OpenAICompletionsAIModel, OpenAIResponsesAIModel + +load_dotenv(find_dotenv(usecwd=True)) app = App(plugins=[DevToolsPlugin()]) -responses_openai_ai_model = OpenAIResponsesAIModel(stateful=True) -chat_memory = ListMemory() + +def get_required_env(key: str) -> str: + value = getenv(key) + if not value: + raise ValueError(f"Required environment variable {key} is not set") + return value + + +AZURE_OPENAI_MODEL = get_required_env("AZURE_OPENAI_MODEL") + + +# GitHub PAT for MCP server (optional) +def get_optional_env(key: str) -> str | None: + return getenv(key) + + +# This example uses a PersonalAccessToken, but you may get +# the user's oauth token as well by getting them to sign in +# and then using app.sign_in to get their token. +GITHUB_PAT = get_optional_env("GITHUB_PAT") + +# Set up AI models +completions_model = OpenAICompletionsAIModel(model=AZURE_OPENAI_MODEL) +responses_model = OpenAIResponsesAIModel(model=AZURE_OPENAI_MODEL, stateful=True) + +# Configure MCP Client Plugin with multiple remote servers (as shown in docs) mcp_plugin = McpClientPlugin() + +# Add multiple MCP servers to demonstrate the concept from documentation mcp_plugin.use_mcp_server("https://learn.microsoft.com/api/mcp") -responses_agent = Agent(responses_openai_ai_model, memory=chat_memory, plugins=[mcp_plugin]) +# Add GitHub MCP server with authentication headers (demonstrates header functionality) +if GITHUB_PAT: + mcp_plugin.use_mcp_server( + "https://api.githubcopilot.com/mcp/", McpClientPluginParams(headers={"Authorization": f"Bearer {GITHUB_PAT}"}) + ) + print("✅ GitHub MCP server configured with authentication") +else: + print("⚠️ GITHUB_PAT not found - GitHub MCP server not configured") + print(" Set GITHUB_PAT environment variable to enable GitHub MCP integration") +# Example of additional servers (commented out - would need actual working endpoints): +# mcp_plugin.use_mcp_server("https://example.com/mcp/weather") +# mcp_plugin.use_mcp_server("https://example.com/mcp/pokemon") + +# Memory for stateful conversations +chat_memory = ListMemory() + +# Agent using Responses API with MCP tools +responses_agent = Agent(responses_model, memory=chat_memory, plugins=[mcp_plugin]) + +# ChatPrompt with MCP tools (demonstrating docs example) +chat_prompt = ChatPrompt(completions_model, plugins=[mcp_plugin]) + + +# Pattern-based handlers to demonstrate different MCP usage patterns + + +@app.on_message_pattern(re.compile(r"^agent\s+(.+)", re.IGNORECASE)) +async def handle_agent_chat(ctx: ActivityContext[MessageActivity]): + """Handle 'agent ' command using Agent with MCP tools (stateful)""" + match = re.match(r"^agent\s+(.+)", ctx.activity.text, re.IGNORECASE) + if match: + query = match.group(1).strip() + + print(f"[AGENT] Processing: {query}") + await ctx.send(TypingActivityInput()) + + # Use Agent with MCP tools (stateful conversation) + result = await responses_agent.send(query) + if result.response.content: + message = MessageActivityInput(text=result.response.content).add_ai_generated() + await ctx.send(message) + + +@app.on_message_pattern(re.compile(r"^prompt\s+(.+)", re.IGNORECASE)) +async def handle_prompt_chat(ctx: ActivityContext[MessageActivity]): + """Handle 'prompt ' command using ChatPrompt with MCP tools (stateless)""" + match = re.match(r"^prompt\s+(.+)", ctx.activity.text, re.IGNORECASE) + if match: + query = match.group(1).strip() + + print(f"[PROMPT] Processing: {query}") + await ctx.send(TypingActivityInput()) + + # Use ChatPrompt with MCP tools (demonstrates docs pattern) + result = await chat_prompt.send( + input=query, + instructions=( + "You are a helpful assistant with access to remote MCP tools.Use them to help answer questions." + ), + ) + + if result.response.content: + message = MessageActivityInput(text=result.response.content).add_ai_generated() + await ctx.send(message) + + +@app.on_message_pattern(re.compile(r"^mcp\s+info", re.IGNORECASE)) +async def handle_mcp_info(ctx: ActivityContext[MessageActivity]): + """Handle 'mcp info' command to show available MCP servers and tools""" + # Build server list dynamically based on what's configured + servers_info = "**Connected MCP Servers:**\n" + servers_info += "• `https://learn.microsoft.com/api/mcp` - Microsoft Learn API\n" + + if GITHUB_PAT: + servers_info += "• `https://api.githubcopilot.com/mcp/` - GitHub Copilot API (authenticated)\n" + else: + servers_info += "• GitHub MCP server (not configured - set GITHUB_PAT env var)\n" + + info_text = ( + "🔗 **MCP Client Information**\n\n" + f"{servers_info}\n" + "**Authentication Demo:**\n" + "• GitHub server uses Bearer token authentication via headers\n" + "• Example: `headers={'Authorization': f'Bearer {GITHUB_PAT}'}`\n\n" + "**Usage Patterns:**\n" + "• `agent ` - Use stateful Agent with MCP tools\n" + "• `prompt ` - Use stateless ChatPrompt with MCP tools\n" + "• `mcp info` - Show this information\n\n" + "**How it works:**\n" + "1. MCP Client connects to remote servers via SSE protocol\n" + "2. Headers (like Authorization) are passed with each request\n" + "3. Remote tools are loaded and integrated with ChatPrompt/Agent\n" + "4. LLM can call remote tools as needed to answer your questions" + ) + await ctx.reply(info_text) +# Fallback handler for general chat (uses Agent by default) @app.on_message -async def handle_message(ctx: ActivityContext[MessageActivity]): - """Handle message activities using the new generated handler system.""" - print(f"[GENERATED onMessage] Message received: {ctx.activity.text}") - print(f"[GENERATED onMessage] From: {ctx.activity.from_}") +async def handle_fallback_message(ctx: ActivityContext[MessageActivity]): + """Fallback handler using Agent with MCP tools""" + print(f"[FALLBACK] Message received: {ctx.activity.text}") + print(f"[FALLBACK] From: {ctx.activity.from_}") await ctx.send(TypingActivityInput()) + # Use Agent with MCP tools for general conversation result = await responses_agent.send(ctx.activity.text) if result.response.content: - await ctx.reply(result.response.content) + message = MessageActivityInput(text=result.response.content).add_ai_generated() + await ctx.send(message) if __name__ == "__main__": diff --git a/tests/mcp-server/README.md b/tests/mcp-server/README.md index e69de29b..0a98d909 100644 --- a/tests/mcp-server/README.md +++ b/tests/mcp-server/README.md @@ -0,0 +1,133 @@ +# Sample: MCP Server + +A comprehensive test demonstrating MCP (Machine Conversation Protocol) server functionality using the Microsoft Teams Python SDK. + +## Prerequisites + +- Python 3.12 or later +- UV package manager +- An Microsoft 365 development account. If you don't have one, you can get one for free by signing up for the [Microsoft 365 Developer Program](https://developer.microsoft.com/microsoft-365/dev-program). + +## Setup + +1. Install dependencies: + +```bash +uv sync +``` + +2. Set up your `.env` file (if needed for Teams connectivity) + +## Run + +```bash +# Activate virtual environment +source .venv/bin/activate # On macOS/Linux +# .venv\Scripts\Activate # On Windows + +# Run the MCP server +python tests/mcp-server/src/main.py +``` + +## Features Demonstrated + +### Core MCP Server Functionality +- **MCP Server Plugin** - Converting a Teams App into an MCP server using `McpServerPlugin` +- **Custom Server Name** - Configuring server with custom name (`test-mcp`) +- **DevTools Integration** - MCP request inspection through DevToolsPlugin +- **Multiple Tool Registration** - Exposing various tools to MCP clients + +### Available Tools + +| Tool | Description | Parameters | Example Usage | +|------|-------------|------------|---------------| +| `echo` | Echo back input text | `input: str` | Echo functionality from docs | +| `get_weather` | Get weather for a location | `location: str` | Always returns "sunny" | +| `calculate` | Basic arithmetic operations | `operation: str, a: float, b: float` | add, subtract, multiply, divide | +| `alert` | Send proactive message to Teams user | `user_id: str, message: str` | Human-in-the-loop notifications | + +### Proactive Messaging (Message Piping) + +The server demonstrates the key documentation feature of "piping messages to the user": + +1. **Conversation Storage** - Stores user conversation IDs when they message the bot +2. **User Validation** - The alert tool validates if a user exists in storage +3. **Proactive Messaging** - Simulates sending notifications back to Teams users + +#### How it works: +1. User sends any message to the bot conversation ID gets stored +2. External MCP client calls the `alert` tool with `user_id` and `message` +3. Server looks up the stored conversation ID and sends proactive message + +### Architecture + +The MCP server is available at `/mcp` endpoint (default) and exposes: +- **Tools** - Callable functions with typed parameters +- **Resources** - (Not implemented in this test) +- **Prompts** - (Not implemented in this test) + +## Testing the MCP Server + +### 1. Start the Server +```bash +python tests/mcp-server/src/main.py +``` + +### 2. Send a Message via Teams +Send any message to store your conversation ID: +``` +Hello MCP server! +``` + +Response will show your stored conversation ID. + +### 3. Test MCP Tools (via MCP Client) +The tools can be called by any MCP-compatible client: + +**Echo Tool:** +```json +{ + "tool": "echo", + "params": { + "input": "Hello from MCP client" + } +} +``` + +**Calculator Tool:** +```json +{ + "tool": "calculate", + "params": { + "operation": "add", + "a": 10, + "b": 5 + } +} +``` + +**Alert Tool (Proactive Messaging):** +```json +{ + "tool": "alert", + "params": { + "user_id": "your-teams-user-id", + "message": "Hello from MCP!" + } +} +``` + +### 4. Monitor in DevTools +- Navigate to the DevTools interface +- View MCP requests and responses in real-time +- Similar to the Activities tab for regular Teams interactions + +## Key Implementation Notes + +- **Plugin Configuration**: Uses `McpServerPlugin(name="test-mcp")` as shown in documentation +- **Tool Registration**: Each tool is registered with `mcp_server_plugin.use_tool(Function(...))` +- **Type Safety**: All parameters use Pydantic models for validation +- **Conversation Tracking**: Stores `user_id conversation_id` mapping for proactive messaging +- **Error Handling**: Proper validation and error responses for invalid tool calls + +This implementation covers all the core concepts from the MCP server documentation and provides a solid foundation for testing and development. \ No newline at end of file diff --git a/tests/mcp-server/src/main.py b/tests/mcp-server/src/main.py index 3d7824e9..7ff8489d 100644 --- a/tests/mcp-server/src/main.py +++ b/tests/mcp-server/src/main.py @@ -4,6 +4,7 @@ """ import asyncio +from typing import Dict from microsoft.teams.ai import Function from microsoft.teams.api.activities.message.message import MessageActivity @@ -13,9 +14,25 @@ from microsoft.teams.mcpplugin import McpServerPlugin from pydantic import BaseModel -mcp_server_plugin = McpServerPlugin() +# Configure MCP server with custom name (as shown in docs) +mcp_server_plugin = McpServerPlugin( + name="test-mcp", +) + +# Storage for conversation IDs (for proactive messaging) +conversation_storage: Dict[str, str] = {} + + +# Echo tool from documentation example +class EchoParams(BaseModel): + input: str + + +async def echo_handler(params: EchoParams) -> str: + return f"You said {params.input}" +# Weather tool (existing) class GetWeatherParams(BaseModel): location: str @@ -44,7 +61,42 @@ async def calculate_handler(params: CalculateParams) -> str: return "Unknown operation" -# Direct function call usage +# Alert tool for proactive messaging (as mentioned in docs) +class AlertParams(BaseModel): + user_id: str + message: str + + +async def alert_handler(params: AlertParams) -> str: + """ + Send proactive message to user via Teams. + This demonstrates the "piping messages to user" feature from docs. + """ + # 1. Validate if the incoming request is allowed to send messages + if not params.user_id or not params.message: + return "Invalid parameters: user_id and message are required" + + # 2. Fetch the correct conversation ID for the given user + conversation_id = conversation_storage.get(params.user_id) + if not conversation_id: + return f"No conversation found for user {params.user_id}. User needs to message the bot first." + + # 3. Send proactive message (simplified - in real implementation would use proper proactive messaging) + await app.send(conversation_id=conversation_id, activity=params.message) + return f"Alert sent to user {params.user_id}: {params.message} (conversation: {conversation_id})" + + +# Register echo tool (from documentation) +mcp_server_plugin.use_tool( + Function( + name="echo", + description="echo back whatever you said", + parameter_schema=EchoParams, + handler=echo_handler, + ) +) + +# Register weather tool mcp_server_plugin.use_tool( Function( name="get_weather", @@ -54,7 +106,7 @@ async def calculate_handler(params: CalculateParams) -> str: ) ) -# Second tool registration +# Register calculator tool mcp_server_plugin.use_tool( Function( name="calculate", @@ -64,12 +116,38 @@ async def calculate_handler(params: CalculateParams) -> str: ) ) +# Register alert tool for proactive messaging +mcp_server_plugin.use_tool( + Function( + name="alert", + description="Send proactive message to a Teams user", + parameter_schema=AlertParams, + handler=alert_handler, + ) +) + app = App(plugins=[mcp_server_plugin, DevToolsPlugin()]) @app.on_message async def handle_message(ctx: ActivityContext[MessageActivity]): - await ctx.reply(f"You said {ctx.activity.text}") + """ + Handle incoming messages and store conversation IDs for proactive messaging. + This demonstrates the conversation ID storage mentioned in the docs. + """ + # Store conversation ID for this user (for proactive messaging) + user_id = ctx.activity.from_.id + conversation_id = ctx.activity.conversation.id + conversation_storage[user_id] = conversation_id + + print(f"User {ctx.activity.from_} just sent a message!") + + # Echo back the message with info about stored conversation + await ctx.reply( + f"You said: {ctx.activity.text}\n\n" + f"📝 Stored conversation ID `{conversation_id}` for user `{user_id}` " + f"(for proactive messaging via MCP alert tool)" + ) if __name__ == "__main__": From 8d899b4035fa5fb4aa86228cc30d291973100c88 Mon Sep 17 00:00:00 2001 From: heyitsaamir Date: Fri, 12 Sep 2025 15:53:17 -0700 Subject: [PATCH 2/2] fix readme --- packages/apps/src/microsoft/teams/apps/app.py | 2 +- tests/mcp-client/README.md | 185 ------------------ tests/mcp-server/README.md | 122 ------------ 3 files changed, 1 insertion(+), 308 deletions(-) diff --git a/packages/apps/src/microsoft/teams/apps/app.py b/packages/apps/src/microsoft/teams/apps/app.py index 39d9fb29..55633357 100644 --- a/packages/apps/src/microsoft/teams/apps/app.py +++ b/packages/apps/src/microsoft/teams/apps/app.py @@ -270,7 +270,7 @@ async def send(self, conversation_id: str, activity: str | ActivityParams | Adap """Send an activity proactively.""" if self.id is None or self.name is None: - raise ValueError(f"app not started {self.tokens.bot}") + raise ValueError("app not started") conversation_ref = ConversationReference( channel_id="msteams", diff --git a/tests/mcp-client/README.md b/tests/mcp-client/README.md index c69212fa..7d9ccead 100644 --- a/tests/mcp-client/README.md +++ b/tests/mcp-client/README.md @@ -1,56 +1,5 @@ # Sample: MCP Client -A comprehensive test demonstrating MCP (Machine Conversation Protocol) client functionality using the Microsoft Teams Python SDK. This test shows how to integrate remote MCP servers into your Teams application, allowing AI agents to access external tools via the SSE protocol. - -## Prerequisites - -- Python 3.12 or later -- UV package manager -- An Microsoft 365 development account. If you don't have one, you can get one for free by signing up for the [Microsoft 365 Developer Program](https://developer.microsoft.com/microsoft-365/dev-program). - -## Setup - -1. Install dependencies: - -```bash -uv sync -``` - -2. Set up your `.env` file with your API keys: - -```bash -# Azure OpenAI (required) -AZURE_OPENAI_API_KEY= -AZURE_OPENAI_ENDPOINT= -AZURE_OPENAI_MODEL= -AZURE_OPENAI_API_VERSION= - -# Alternatively, set the OpenAI API key: -OPENAI_API_KEY= - -# GitHub PAT for MCP server (optional) -GITHUB_PAT= -``` - -## Run - -```bash -# Activate virtual environment -source .venv/bin/activate # On macOS/Linux -# .venv\Scripts\Activate # On Windows - -# Run the MCP client -python tests/mcp-client/src/main.py -``` - -## Features Demonstrated - -### Core MCP Client Functionality -- **Remote MCP Server Integration** - Connect to external MCP servers via SSE protocol -- **Multiple Server Support** - Demonstrate connecting to multiple MCP servers simultaneously -- **ChatPrompt Integration** - Direct integration with ChatPrompt for tool access -- **Agent Integration** - Stateful conversation with MCP tools through Agent pattern -- **DevTools Integration** - Monitor MCP requests and responses in real-time ### Available Commands @@ -61,137 +10,3 @@ python tests/mcp-client/src/main.py | `mcp info` | Show connected MCP servers and usage | `mcp info` | | `` | Fallback to Agent with MCP tools | `Hello, can you help me?` | -### Architecture Patterns - -#### 1. ChatPrompt with MCP (Stateless) -```python -chat_prompt = ChatPrompt( - completions_model, - plugins=[mcp_plugin] -) - -result = await chat_prompt.send( - input=query, - instructions="You are a helpful assistant with access to remote MCP tools." -) -``` - -#### 2. Agent with MCP (Stateful) -```python -responses_agent = Agent( - responses_model, - memory=chat_memory, - plugins=[mcp_plugin] -) - -result = await responses_agent.send(query) -``` - -#### 3. Multiple MCP Server Configuration -```python -mcp_plugin = McpClientPlugin() -mcp_plugin.use_mcp_server("https://learn.microsoft.com/api/mcp") -mcp_plugin.use_mcp_server("https://example.com/mcp/weather") -mcp_plugin.use_mcp_server("https://example.com/mcp/pokemon") -``` - -### Connected MCP Servers - -- **Microsoft Learn API** - `https://learn.microsoft.com/api/mcp` - - Provides access to Microsoft documentation and learning resources - - Demonstrates basic MCP server integration without authentication - -- **GitHub Copilot API** - `https://api.githubcopilot.com/mcp/` (Optional) - - Requires GitHub Personal Access Token (`GITHUB_PAT` environment variable) - - Demonstrates authenticated MCP server integration with headers - - Provides access to GitHub repositories, issues, and code analysis tools - -**Authentication Example:** -```python -from microsoft.teams.mcpplugin import McpClientPluginParams - -# This example uses a PersonalAccessToken, but you may get -# the user's oauth token as well by getting them to sign in -# and then using app.sign_in to get their token. -GITHUB_PAT = getenv("GITHUB_PAT") - -# GitHub MCP server with Bearer token authentication -if GITHUB_PAT: - mcp_plugin.use_mcp_server( - "https://api.githubcopilot.com/mcp/", - McpClientPluginParams(headers={ - "Authorization": f"Bearer {GITHUB_PAT}" - }) - ) -``` - -## How MCP Client Works - -1. **Server Discovery** - MCP Client connects to remote servers via SSE protocol -2. **Tool Loading** - Remote tools are loaded and made available to the LLM -3. **Function Integration** - Tools are treated like local functions by ChatPrompt/Agent -4. **Remote Execution** - When LLM calls a tool, request is sent to remote MCP server -5. **Result Processing** - Response from remote server is returned to LLM for use - -## Testing the MCP Client - -### 1. Start the Client -```bash -python tests/mcp-client/src/main.py -``` - -### 2. Test Different Patterns - -**Stateful Agent Pattern:** -``` -agent Tell me about Microsoft Teams development -``` - -**Stateless ChatPrompt Pattern:** -``` -prompt What are the latest features in .NET? -``` - -**General Chat (uses Agent by default):** -``` -Hello, I need help with Azure functions -``` - -**Get MCP Information:** -``` -mcp info -``` - -### 3. Monitor in DevTools -- Navigate to the DevTools interface -- View MCP requests and responses in real-time -- See which remote tools are being called by the LLM -- Monitor latency and response data from remote servers - -## Key Implementation Details - -- **SSE Protocol** - Uses Server-Sent Events for remote MCP server communication -- **Plugin Architecture** - MCP Client integrates as a plugin with ChatPrompt and Agent -- **Tool Discovery** - Remote tools are automatically discovered and made available -- **Error Handling** - Graceful handling of remote server connectivity issues -- **Memory Management** - Proper memory handling for stateful vs stateless patterns -- **Type Safety** - Full typing support with proper async/await patterns - -## Remote MCP Server Requirements - -For connecting to your own MCP servers, ensure they: - -1. **Support SSE Protocol** - Server must implement Server-Sent Events communication -2. **Follow MCP Specification** - Implement proper MCP protocol for tool definition and execution -3. **HTTPS Endpoint** - Server should be accessible via HTTPS URL -4. **Authentication** - If required, proper header-based authentication support - -## Use Cases Demonstrated - -- **Human-in-the-loop** - Remote tools can ask for user confirmation -- **External APIs** - Access to third-party services via MCP protocol -- **Distributed Architecture** - Tools can be hosted separately from main application -- **Tool Sharing** - Multiple applications can use the same MCP servers -- **Scalability** - Remote tools can be scaled independently - -This implementation provides a complete foundation for integrating external MCP servers into Teams applications, enabling powerful distributed AI agent architectures. \ No newline at end of file diff --git a/tests/mcp-server/README.md b/tests/mcp-server/README.md index 0a98d909..11c0030a 100644 --- a/tests/mcp-server/README.md +++ b/tests/mcp-server/README.md @@ -1,42 +1,5 @@ # Sample: MCP Server -A comprehensive test demonstrating MCP (Machine Conversation Protocol) server functionality using the Microsoft Teams Python SDK. - -## Prerequisites - -- Python 3.12 or later -- UV package manager -- An Microsoft 365 development account. If you don't have one, you can get one for free by signing up for the [Microsoft 365 Developer Program](https://developer.microsoft.com/microsoft-365/dev-program). - -## Setup - -1. Install dependencies: - -```bash -uv sync -``` - -2. Set up your `.env` file (if needed for Teams connectivity) - -## Run - -```bash -# Activate virtual environment -source .venv/bin/activate # On macOS/Linux -# .venv\Scripts\Activate # On Windows - -# Run the MCP server -python tests/mcp-server/src/main.py -``` - -## Features Demonstrated - -### Core MCP Server Functionality -- **MCP Server Plugin** - Converting a Teams App into an MCP server using `McpServerPlugin` -- **Custom Server Name** - Configuring server with custom name (`test-mcp`) -- **DevTools Integration** - MCP request inspection through DevToolsPlugin -- **Multiple Tool Registration** - Exposing various tools to MCP clients - ### Available Tools | Tool | Description | Parameters | Example Usage | @@ -46,88 +9,3 @@ python tests/mcp-server/src/main.py | `calculate` | Basic arithmetic operations | `operation: str, a: float, b: float` | add, subtract, multiply, divide | | `alert` | Send proactive message to Teams user | `user_id: str, message: str` | Human-in-the-loop notifications | -### Proactive Messaging (Message Piping) - -The server demonstrates the key documentation feature of "piping messages to the user": - -1. **Conversation Storage** - Stores user conversation IDs when they message the bot -2. **User Validation** - The alert tool validates if a user exists in storage -3. **Proactive Messaging** - Simulates sending notifications back to Teams users - -#### How it works: -1. User sends any message to the bot conversation ID gets stored -2. External MCP client calls the `alert` tool with `user_id` and `message` -3. Server looks up the stored conversation ID and sends proactive message - -### Architecture - -The MCP server is available at `/mcp` endpoint (default) and exposes: -- **Tools** - Callable functions with typed parameters -- **Resources** - (Not implemented in this test) -- **Prompts** - (Not implemented in this test) - -## Testing the MCP Server - -### 1. Start the Server -```bash -python tests/mcp-server/src/main.py -``` - -### 2. Send a Message via Teams -Send any message to store your conversation ID: -``` -Hello MCP server! -``` - -Response will show your stored conversation ID. - -### 3. Test MCP Tools (via MCP Client) -The tools can be called by any MCP-compatible client: - -**Echo Tool:** -```json -{ - "tool": "echo", - "params": { - "input": "Hello from MCP client" - } -} -``` - -**Calculator Tool:** -```json -{ - "tool": "calculate", - "params": { - "operation": "add", - "a": 10, - "b": 5 - } -} -``` - -**Alert Tool (Proactive Messaging):** -```json -{ - "tool": "alert", - "params": { - "user_id": "your-teams-user-id", - "message": "Hello from MCP!" - } -} -``` - -### 4. Monitor in DevTools -- Navigate to the DevTools interface -- View MCP requests and responses in real-time -- Similar to the Activities tab for regular Teams interactions - -## Key Implementation Notes - -- **Plugin Configuration**: Uses `McpServerPlugin(name="test-mcp")` as shown in documentation -- **Tool Registration**: Each tool is registered with `mcp_server_plugin.use_tool(Function(...))` -- **Type Safety**: All parameters use Pydantic models for validation -- **Conversation Tracking**: Stores `user_id conversation_id` mapping for proactive messaging -- **Error Handling**: Proper validation and error responses for invalid tool calls - -This implementation covers all the core concepts from the MCP server documentation and provides a solid foundation for testing and development. \ No newline at end of file