11 KiB
MCP for Outlook/SharePoint: Can MCP Servers Replace Your Custom Actions?
Your Question
Can you use MCP server functionality to access Outlook emails and SharePoint data, eliminating the need for custom methodOutlook and methodSharepoint actions?
Answer: ✅ Yes, but with important considerations
How MCP Accesses Outlook/SharePoint
MCP Server Architecture
Your System
↓ (MCP Client)
MCP Server (OutlookMCPServer / Microsoft 365 MCP Server)
↓ (Microsoft Graph API + OAuth)
Microsoft 365 (Outlook, SharePoint, OneDrive, Teams)
Authentication Flow
MCP servers use the same authentication as your current methods:
-
OAuth 2.0 Flow:
- User grants permissions via Azure AD
- Access token obtained (same as your
connectionReference) - Token used for Microsoft Graph API calls
-
Permissions Required:
- Outlook:
Mail.ReadWrite,Mail.Send,Mail.ReadWrite.Shared - SharePoint:
Sites.ReadWrite.All,Files.ReadWrite.All
- Outlook:
Key Point: MCP servers need the same OAuth setup as your current methods.
Current Implementation vs MCP Server
Your Current Implementation
methodOutlook.py:
class MethodOutlook(MethodBase):
def _getMicrosoftConnection(self, connectionReference: str):
# Get connection from your system
userConnection = self.services.chat.getUserConnectionFromConnectionReference(connectionReference)
# Get fresh token
token = self.services.chat.getFreshConnectionToken(userConnection.id)
return {
"accessToken": token.tokenAccess,
"refreshToken": token.tokenRefresh,
"scopes": ["Mail.ReadWrite", "Mail.Send", ...]
}
@action
async def readEmails(self, parameters: Dict) -> ActionResult:
connection = self._getMicrosoftConnection(parameters["connectionReference"])
# Direct Microsoft Graph API call
response = requests.get(
f"https://graph.microsoft.com/v1.0/me/messages",
headers={"Authorization": f"Bearer {connection['accessToken']}"}
)
# Process and return ActionResult
Characteristics:
- ✅ Uses your existing
connectionReferencesystem - ✅ Integrates with your
UserConnectionmanagement - ✅ Returns
ActionResult(compatible with your workflow) - ✅ Custom actions tailored to your needs
MCP Server Implementation
OutlookMCPServer (external MCP server):
# MCP Server exposes tools like:
tools = [
{
"name": "outlook.readEmails",
"description": "Read emails from Outlook",
"inputSchema": {
"type": "object",
"properties": {
"folder": {"type": "string"},
"limit": {"type": "integer"}
}
}
},
{
"name": "outlook.sendEmail",
"description": "Send email via Outlook",
"inputSchema": {...}
}
]
# MCP Server handles authentication internally
# Uses Microsoft Graph API (same as your methods)
Characteristics:
- ✅ Standardized MCP protocol
- ✅ Pre-built tools (no custom code needed)
- ⚠️ Needs separate OAuth setup (not integrated with your
connectionReference) - ⚠️ Returns MCP format (needs conversion to
ActionResult)
Comparison: Custom Actions vs MCP Servers
Scenario: Read Outlook Emails
Option A: Your Custom Action (Current)
# In your workflow
result = await executeAction("outlook.readEmails", {
"connectionReference": "conn_msft_123", # Your connection system
"folder": "Inbox",
"limit": 10
})
# Returns: ActionResult with ActionDocument[]
# Integrated with your connection management
# Works seamlessly with your workflow
Pros:
- ✅ Integrated with your
connectionReferencesystem - ✅ Uses your existing
UserConnectionmanagement - ✅ Returns
ActionResult(native format) - ✅ Customizable to your specific needs
- ✅ Token refresh handled by your system
Cons:
- ❌ You maintain the code
- ❌ Need to implement all actions yourself
Option B: MCP Server (Alternative)
# Connect to external MCP server
outlookMcpClient = ExternalMcpClient("http://outlook-mcp-server:8080")
# Call MCP tool
mcpResult = await outlookMcpClient.callTool("outlook.readEmails", {
"folder": "Inbox",
"limit": 10
# Note: No connectionReference - MCP server handles auth internally
})
# Convert MCP result to ActionResult
result = mcpResultToActionResult(mcpResult)
Pros:
- ✅ No custom code to maintain
- ✅ Pre-built tools (readEmails, sendEmail, etc.)
- ✅ Standardized protocol
- ✅ Can use multiple MCP servers (Outlook, SharePoint, etc.)
Cons:
- ❌ Separate OAuth setup (not integrated with your
connectionReference) - ❌ Different authentication flow (MCP server manages tokens)
- ❌ Needs conversion from MCP format to
ActionResult - ❌ Less control over implementation
- ❌ May not match your exact requirements
Critical Question: Authentication
How Does MCP Server Access Your Data?
MCP servers need authentication, just like your current methods:
-
Option 1: MCP Server Manages OAuth
- User authenticates with MCP server
- MCP server stores tokens
- Problem: Separate from your
connectionReferencesystem - Problem: User needs to authenticate twice (your system + MCP server)
-
Option 2: Pass Tokens to MCP Server
- Your system gets token (via
connectionReference) - Pass token to MCP server
- Problem: MCP protocol doesn't standardize token passing
- Problem: Security concern (passing tokens)
- Your system gets token (via
-
Option 3: Custom MCP Server Using Your Connection System
- Create MCP server wrapper around your methods
- Uses your
connectionReferencesystem - This is what the original proposal suggested (but adds overhead)
Practical Assessment
Can You Replace Your Actions with MCP Servers?
Short Answer: Technically yes, but not recommended for these reasons:
❌ Problem 1: Authentication Duplication
Your System:
# User authenticates once
userConnection = createUserConnection("msft", oauthFlow)
# Stored in your system, reusable across all actions
MCP Server:
# User needs to authenticate again
# MCP server manages its own tokens
# Not integrated with your connectionReference system
Impact: Users authenticate twice, tokens managed separately
❌ Problem 2: Integration Complexity
Your Current Flow:
Workflow → ActionExecutor → methodOutlook.readEmails()
↓
Uses connectionReference → Gets token → Calls Graph API
↓
Returns ActionResult (native format)
MCP Flow:
Workflow → ActionExecutor → MCP Client → MCP Server
↓
MCP Server manages auth → Calls Graph API
↓
Returns MCP format → Convert to ActionResult
Impact: More layers, more complexity, more points of failure
❌ Problem 3: Loss of Control
Your Custom Actions:
- ✅ Full control over implementation
- ✅ Customize to your exact needs
- ✅ Integrate with your workflow seamlessly
- ✅ Use your connection management
MCP Servers:
- ❌ Limited to what MCP server provides
- ❌ Can't customize easily
- ❌ Need to adapt to MCP format
- ❌ Separate authentication system
When MCP Servers Make Sense
✅ Use Case 1: External Tools You Don't Want to Maintain
Example: Slack, GitHub, Postgres MCP servers
# Use external MCP server for Slack (you don't have Slack actions)
slackMcpClient = ExternalMcpClient("http://slack-mcp-server:8080")
result = await slackMcpClient.callTool("slack.sendMessage", {...})
Value: ✅ No need to build/maintain Slack integration
✅ Use Case 2: Standard Tools with Standard Authentication
Example: Public APIs with API keys (not OAuth)
# Use MCP server for public API (simple API key auth)
weatherMcpClient = ExternalMcpClient("http://weather-mcp-server:8080")
result = await weatherMcpClient.callTool("weather.getForecast", {...})
Value: ✅ Simple integration, no complex auth
❌ Don't Use MCP Servers When:
- You already have working custom actions (like Outlook/SharePoint)
- Authentication is complex (OAuth with your connection system)
- You need tight integration with your workflow
- You need customization beyond what MCP server provides
Recommendation
For Outlook/SharePoint: Keep Your Custom Actions
Why:
- ✅ Already working: Your
methodOutlookandmethodSharepointwork well - ✅ Integrated authentication: Uses your
connectionReferencesystem - ✅ Native format: Returns
ActionResultdirectly - ✅ Customizable: Tailored to your specific needs
- ✅ No duplication: Single authentication flow
Don't replace with MCP servers because:
- ❌ Would require separate OAuth setup
- ❌ Would need format conversion
- ❌ Would lose integration with your connection system
- ❌ Adds complexity without clear benefit
Use MCP Servers For:
- New external tools you don't want to build (Slack, GitHub, etc.)
- Simple integrations with standard APIs
- Tools with simple authentication (API keys, not OAuth)
Alternative: Hybrid Approach
Use MCP Servers for New Tools, Keep Custom Actions for Existing
class ActionExecutor:
async def executeAction(self, methodName: str, actionName: str, parameters: Dict):
# Check if it's a custom action (Outlook, SharePoint, AI)
if methodName in ["outlook", "sharepoint", "ai"]:
# Use your custom actions (existing code)
return await self._executeCustomAction(methodName, actionName, parameters)
# Check if it's an external MCP tool
elif methodName in self.mcpClients:
# Use MCP server
mcpClient = self.mcpClients[methodName]
toolName = f"{methodName}.{actionName}"
mcpResult = await mcpClient.callTool(toolName, parameters)
return self._mcpResultToActionResult(mcpResult)
else:
raise ValueError(f"Unknown method: {methodName}")
Benefits:
- ✅ Keep existing Outlook/SharePoint actions (working, integrated)
- ✅ Use MCP servers for new external tools (Slack, GitHub, etc.)
- ✅ Best of both worlds
Conclusion
Can you use MCP servers for Outlook/SharePoint?
- ✅ Technically yes - MCP servers exist and can access Outlook/SharePoint
- ❌ Practically no - Your custom actions are better integrated
How does MCP access your data?
- MCP servers use Microsoft Graph API + OAuth (same as your methods)
- But they need separate OAuth setup (not integrated with your
connectionReference)
Recommendation:
- ✅ Keep your custom Outlook/SharePoint actions (they're better integrated)
- ✅ Use MCP servers for new external tools (Slack, GitHub, etc.)
- ✅ Hybrid approach: Custom actions for existing, MCP for new
The real value of MCP: Easy integration of external tools you don't want to build, not replacing working custom actions.