Skip to content

Commit 6f1aa2a

Browse files
committed
Create MCP Server
1 parent d4f3440 commit 6f1aa2a

File tree

4 files changed

+452
-2
lines changed

4 files changed

+452
-2
lines changed

packages/mcp/pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ keywords = ["microsoft", "teams", "ai", "bot", "agents"]
1010
license = "MIT"
1111
dependencies = [
1212
"mcp>=1.13.1",
13+
"fastmcp>=0.5.0",
14+
"microsoft.teams.apps",
15+
"microsoft.teams.ai",
16+
"starlette",
1317
]
1418

1519
[build-system]

packages/mcp/src/microsoft/teams/mcp/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
"""
55

66
from .ai_plugin import McpClientPlugin, McpClientPluginParams, McpToolDetails
7+
from .server_plugin import McpServerPlugin
78

8-
__all__ = ["McpClientPlugin", "McpClientPluginParams", "McpToolDetails"]
9+
__all__ = ["McpClientPlugin", "McpClientPluginParams", "McpToolDetails", "McpServerPlugin"]
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
"""
2+
Copyright (c) Microsoft Corporation. All rights reserved.
3+
Licensed under the MIT License.
4+
"""
5+
6+
import importlib.metadata
7+
from inspect import isawaitable
8+
from logging import Logger
9+
from typing import Annotated, Any
10+
11+
from fastmcp import FastMCP
12+
from fastmcp.tools import FunctionTool
13+
from microsoft.teams.ai import Function
14+
from microsoft.teams.apps import (
15+
DependencyMetadata,
16+
HttpPlugin,
17+
LoggerDependencyOptions,
18+
Plugin,
19+
PluginBase,
20+
PluginStartEvent,
21+
)
22+
from pydantic import BaseModel
23+
from starlette.routing import Mount
24+
25+
try:
26+
version = importlib.metadata.version("microsoft-teams-mcp")
27+
except importlib.metadata.PackageNotFoundError:
28+
version = "0.0.1-alpha.1"
29+
30+
31+
@Plugin(
32+
name="mcp-server", version=version, description="MCP server plugin that exposes Teams AI functions as MCP tools"
33+
)
34+
class McpServerPlugin(PluginBase):
35+
"""
36+
MCP Server Plugin for Teams Apps.
37+
38+
This plugin wraps FastMCP and provides a bridge between Teams AI Functions
39+
and MCP tools, exposing them via streamable HTTP transport.
40+
"""
41+
42+
# Dependency injection
43+
logger: Annotated[Logger, LoggerDependencyOptions()]
44+
http: Annotated[HttpPlugin, DependencyMetadata()]
45+
46+
def __init__(
47+
self,
48+
name: str = "teams-mcp-server",
49+
path: str = "/mcp",
50+
):
51+
"""
52+
Initialize the MCP server plugin.
53+
54+
Args:
55+
name: The name of the MCP server
56+
path: The path to mount the MCP server on (default: /mcp)
57+
"""
58+
self.mcp_server = FastMCP(name)
59+
self.path = path
60+
self._mounted = False
61+
62+
@property
63+
def server(self) -> FastMCP:
64+
"""Get the underlying FastMCP server."""
65+
return self.mcp_server
66+
67+
def add_function(self, function: Function[BaseModel]) -> "McpServerPlugin":
68+
"""
69+
Add a Teams AI function as an MCP tool.
70+
71+
Args:
72+
function: The Teams AI function to register as an MCP tool
73+
74+
Returns:
75+
Self for method chaining
76+
"""
77+
try:
78+
# Prepare parameter schema for FastMCP
79+
parameter_schema = (
80+
function.parameter_schema
81+
if isinstance(function.parameter_schema, dict)
82+
else function.parameter_schema.model_json_schema()
83+
)
84+
85+
# Create wrapper handler that converts kwargs to the expected format
86+
async def wrapped_handler(**kwargs: Any) -> Any:
87+
try:
88+
if isinstance(function.parameter_schema, type):
89+
# parameter_schema is a Pydantic model class - instantiate it
90+
params = function.parameter_schema(**kwargs)
91+
result = function.handler(params)
92+
else:
93+
# parameter_schema is a dict - pass kwargs directly
94+
result = function.handler(**kwargs)
95+
96+
# Handle both sync and async handlers
97+
if isawaitable(result):
98+
return await result
99+
return result
100+
except Exception as e:
101+
self.logger.error(f"Function execution failed for '{function.name}': {e}")
102+
raise
103+
104+
function_tool = FunctionTool(
105+
name=function.name, description=function.description, parameters=parameter_schema, fn=wrapped_handler
106+
)
107+
self.mcp_server.add_tool(function_tool)
108+
109+
self.logger.info(f"Registered Teams AI function '{function.name}' as MCP tool")
110+
111+
return self
112+
except Exception as e:
113+
self.logger.error(f"Failed to register function '{function.name}' as MCP tool: {e}")
114+
raise
115+
116+
async def on_init(self) -> None:
117+
"""Initialize the plugin."""
118+
self.logger.info("Initializing MCP server plugin")
119+
120+
async def on_start(self, event: PluginStartEvent) -> None:
121+
"""Start the plugin - mount MCP server on HTTP plugin."""
122+
if self._mounted:
123+
self.logger.warning("MCP server already mounted")
124+
return
125+
126+
try:
127+
# Mount the MCP streamable HTTP app on the existing FastAPI server
128+
mount_route = Mount(self.path, app=self.mcp_server.streamable_http_app())
129+
self.http.app.router.routes.append(mount_route)
130+
131+
self._mounted = True
132+
133+
self.logger.info(f"MCP server mounted at http://localhost:{event.port}{self.path}")
134+
except Exception as e:
135+
self.logger.error(f"Failed to mount MCP server: {e}")
136+
raise
137+
138+
async def on_stop(self) -> None:
139+
"""Stop the plugin - clean shutdown of MCP server."""
140+
if self._mounted:
141+
self.logger.info("MCP server shutting down")
142+
self._mounted = False

0 commit comments

Comments
 (0)