18 KiB
MCP (Model Context Protocol) Architecture Analysis
MCP Overview
Model Context Protocol (MCP) is an open standard introduced by Anthropic (November 2024) that provides a standardized way for AI systems to interact with external tools, data sources, and systems.
Core MCP Concepts
- MCP Server: Provides capabilities (tools, resources, prompts) to AI clients
- MCP Client: AI system that uses MCP servers to access external capabilities
- Tools: Executable functions that the AI can call (similar to actions)
- Resources: Readable data sources (files, databases, APIs)
- Prompts: Pre-defined prompt templates with placeholders
MCP Communication Model
- Protocol: JSON-RPC 2.0 over stdio, HTTP, or WebSocket
- Request/Response: Client sends requests, server responds
- Discovery: Client discovers available tools/resources/prompts from server
- Execution: Client calls tools, reads resources, uses prompts
✅ MCP is Model-Agnostic
Critical Point: MCP is NOT limited to Anthropic's Claude models. It is designed as an open, model-agnostic standard that works with:
- ✅ Anthropic Claude (Claude 3.5 Sonnet, Opus, Haiku)
- ✅ OpenAI GPT (GPT-4, GPT-4 Turbo, GPT-3.5)
- ✅ Google Gemini (Gemini Pro, Gemini Ultra)
- ✅ Other LLM Providers (any provider that supports function calling/tool use)
- ✅ Multi-Model Systems (systems that dynamically select models)
How It Works:
- MCP servers are independent of the AI model
- MCP clients can be implemented for any AI provider
- The protocol standardizes tool/resource interfaces, not model-specific APIs
- Your dynamic model selection can work through MCP, not instead of it
Your Architecture Compatibility:
- ✅ Dynamic Model Selection: MCP works with your per-call model selection
- ✅ Failover Mechanism: MCP servers don't care which model calls them
- ✅ Model-Aware Chunking: MCP tools receive content, model selection happens before MCP call
- ✅ Operation Type Selection: MCP tools can be selected based on
operationType(same as current actions)
Current Architecture vs MCP
Current Architecture
Structure:
User Request
↓
WorkflowProcessor
↓
Mode (Dynamic/Actionplan)
↓
ActionExecutor
↓
Methods (methodAi, methodOutlook, methodSharepoint)
↓
Actions (process, readEmails, uploadFiles)
↓
Services (aiService, chatService, generationService)
Key Characteristics:
- Action-based: Actions are discovered dynamically via
@actiondecorator - Service-oriented: Services provide capabilities (AI, chat, generation, extraction)
- Workflow-driven: Sequential execution with state management
- Type-safe: Pydantic models for all parameters/returns
- Two-stage planning: Stage 1 (action selection) + Stage 2 (parameter generation)
MCP Architecture
Structure:
AI Client (Any LLM: Claude, GPT-4, Gemini, etc.)
↓
MCP Client Library (model-agnostic)
↓
MCP Server (provides tools/resources/prompts)
↓
External System (database, API, file system, etc.)
Key Characteristics:
- Model-Agnostic: Works with any AI provider (not just Anthropic)
- Tool-based: Tools are registered with MCP server
- Resource-based: Resources provide read-only data access
- Prompt-based: Pre-defined prompts with placeholders
- Discovery-driven: Client discovers capabilities at runtime
- Standardized: JSON-RPC protocol for all communication
Compatibility Analysis
✅ Compatible Aspects
-
Tool/Action Equivalence:
- MCP Tools ≈ Current Actions
- Both are executable functions with parameters
- Both support discovery and execution
- Both can return results
-
Resource/Document Equivalence:
- MCP Resources ≈ Current Document References
- Both provide read-only data access
- Both support discovery and reading
- Both can be referenced by URI/identifier
-
Type Safety:
- MCP: Uses JSON Schema for tool parameters
- Current: Uses Pydantic models for action parameters
- Compatibility: Both provide type validation
-
Modularity:
- MCP: Servers are modular and composable
- Current: Methods are modular and composable
- Compatibility: Both support modular architecture
⚠️ Incompatible Aspects
-
Execution Model:
- MCP: Client-driven (AI decides which tools to call)
- Current: Workflow-driven (predefined action sequence)
- Conflict: MCP assumes AI autonomy, current system uses planning
-
Planning vs Execution:
- MCP: AI directly calls tools based on user request
- Current: Two-stage planning (select action → generate parameters → execute)
- Conflict: MCP doesn't have planning phase
-
State Management:
- MCP: Stateless tool calls
- Current: Stateful workflow (rounds, tasks, actions)
- Conflict: MCP doesn't track workflow state
-
Service Dependencies:
- MCP: Servers are independent
- Current: Services have dependencies (chatService → aiService)
- Conflict: MCP assumes flat server structure
Integration Possibilities
Option 1: MCP as Action Backend (Recommended)
Concept: Expose current actions as MCP tools, but keep workflow planning
Architecture:
User Request
↓
WorkflowProcessor (planning phase - unchanged)
↓
ActionExecutor
↓
Model Selection (dynamic - based on operationType, priority, etc.)
├─> Select Model: GPT-4, Claude, Gemini, etc. (unchanged)
↓
MCP Client (model-agnostic - works with any selected model)
↓
MCP Servers (wrapping current methods as tools)
├─> MCP Server: "ai" (provides ai.process, ai.webResearch, etc.)
├─> MCP Server: "outlook" (provides outlook.readEmails, etc.)
└─> MCP Server: "sharepoint" (provides sharepoint.uploadFiles, etc.)
↓
Methods (unchanged implementation)
Benefits:
- ✅ Keep existing workflow planning
- ✅ Keep dynamic model selection (MCP is model-agnostic)
- ✅ Standardize action interface (MCP tools)
- ✅ Enable external MCP servers (third-party tools)
- ✅ Maintain type safety (MCP JSON Schema ↔ Pydantic)
- ✅ Model selection happens before MCP call (MCP doesn't care which model)
Implementation:
# MCP Server wrapper for methods
class MethodMcpServer:
"""MCP server that exposes method actions as tools (model-agnostic)"""
def __init__(self, method: MethodBase):
self.method = method
self.tools = self._discoverTools()
def _discoverTools(self) -> List[McpTool]:
"""Convert @action methods to MCP tools"""
tools = []
for actionName, actionInfo in self.method.actions.items():
tool = McpTool(
name=f"{self.method.name}.{actionName}",
description=actionInfo['description'],
inputSchema=self._pydanticToJsonSchema(actionInfo['parameters'])
)
tools.append(tool)
return tools
async def callTool(self, name: str, arguments: Dict[str, Any]) -> Any:
"""Execute action via MCP tool call (model-agnostic)"""
# MCP server doesn't know/care which AI model called it
methodName, actionName = name.split('.', 1)
return await self.method.actions[actionName]['method'](arguments)
# MCP Client with dynamic model selection
class McpClientWithModelSelection:
"""MCP client that supports dynamic model selection"""
def __init__(self, aiObjects: Any, mcpServers: List[MethodMcpServer]):
self.aiObjects = aiObjects # Your existing aiObjects (handles model selection)
self.mcpServers = mcpServers
async def callTool(
self,
toolName: str,
arguments: Dict[str, Any],
operationType: OperationTypeEnum,
options: AiCallOptions
) -> Any:
"""Call MCP tool with dynamic model selection"""
# 1. Select model (your existing logic - unchanged)
selectedModel = self.aiObjects.selectModel(
operationType=operationType,
priority=options.priority,
contentType=options.contentType
)
# 2. Call MCP tool (model-agnostic - works with any model)
server = self._findServerForTool(toolName)
result = await server.callTool(toolName, arguments)
# 3. Model selection and failover handled by aiObjects (unchanged)
return result
Option 2: MCP as External Tool Integration
Concept: Use MCP to integrate external tools, keep internal actions as-is
Architecture:
User Request
↓
WorkflowProcessor
↓
ActionExecutor
├─> Internal Actions (unchanged - methodAi, methodOutlook, etc.)
└─> External MCP Tools (new - via MCP client)
├─> MCP Server: "slack" (external)
├─> MCP Server: "github" (external)
└─> MCP Server: "database" (external)
Benefits:
- ✅ Keep existing architecture unchanged
- ✅ Add external tools via MCP
- ✅ Standardized interface for external integrations
Implementation:
# Add MCP client to ActionExecutor
class ActionExecutor:
def __init__(self, services, mcpClients: List[McpClient]):
self.services = services
self.mcpClients = mcpClients # External MCP servers
async def executeAction(self, action: str, parameters: Dict):
# Check if action is internal
if '.' in action and action.split('.')[0] in methods:
return await self._executeInternalAction(action, parameters)
# Check if action is external MCP tool
for mcpClient in self.mcpClients:
if mcpClient.hasTool(action):
return await mcpClient.callTool(action, parameters)
raise ValueError(f"Unknown action: {action}")
Option 3: Hybrid Approach (Best of Both)
Concept: Internal actions remain direct, external tools via MCP, unified interface
Architecture:
User Request
↓
WorkflowProcessor
↓
ActionExecutor
├─> Internal Actions (direct method calls - fast, type-safe)
└─> External Tools (MCP client - standardized, extensible)
Benefits:
- ✅ Keep internal actions fast (no MCP overhead)
- ✅ Standardize external tool integration
- ✅ Unified action interface for planning
Detailed Comparison
Tool/Action Discovery
MCP:
{
"tools": [
{
"name": "read_file",
"description": "Read a file",
"inputSchema": {
"type": "object",
"properties": {
"path": {"type": "string"}
}
}
}
]
}
Current:
@action
async def process(parameters: AiProcessParameters) -> ActionResult:
"""AI processing action"""
# Action discovered via @action decorator
# Parameters: Pydantic model (AiProcessParameters)
Compatibility: ✅ High - Both support discovery and type validation
Tool/Action Execution
MCP:
{
"method": "tools/call",
"params": {
"name": "read_file",
"arguments": {"path": "/tmp/file.txt"}
}
}
Current:
result = await executeAction(
methodName="ai",
actionName="process",
selection=ActionDefinition(
action="ai.process",
parameters={"aiPrompt": "...", "contentParts": [...]}
)
)
Compatibility: ✅ High - Both execute functions with parameters
Resource/Document Access
MCP:
{
"resources": [
{
"uri": "file:///tmp/document.pdf",
"name": "Document PDF",
"mimeType": "application/pdf"
}
]
}
Current:
documentList = DocumentReferenceList([
DocumentListReference(label="task1_results"),
DocumentItemReference(documentId="doc_123")
])
Compatibility: ⚠️ Medium - Different reference models, but both provide data access
Planning vs Direct Execution
MCP:
User: "Read file X and summarize it"
↓
AI: [Discovers tools] → [Calls read_file] → [Calls summarize]
↓
Result: Summary
Current:
User: "Read file X and summarize it"
↓
Planning: [Stage 1: Select action] → [Stage 2: Generate parameters]
↓
Execution: [Execute action with parameters]
↓
Result: Summary
Compatibility: ⚠️ Low - Different execution models
Integration Strategy
Recommended: Option 1 (MCP as Action Backend)
Why:
- Standardization: Actions become MCP tools (standard interface)
- Extensibility: Can add external MCP servers easily
- Compatibility: Keep existing workflow planning
- Type Safety: MCP JSON Schema ↔ Pydantic models
Implementation Steps:
-
Create MCP Server Wrapper:
class MethodMcpServer: """Wraps method actions as MCP tools""" def __init__(self, method: MethodBase): self.method = method def getTools(self) -> List[McpTool]: """Convert @action methods to MCP tools""" # Discover actions via @action decorator # Convert Pydantic models to JSON Schema # Return MCP tool definitions -
Create MCP Client in ActionExecutor:
class ActionExecutor: def __init__(self, services, mcpServers: List[MethodMcpServer]): self.services = services self.mcpServers = mcpServers async def executeAction(self, action: str, parameters: Dict): # Find MCP server that provides this tool server = self._findServerForAction(action) return await server.callTool(action, parameters) -
Convert Pydantic to JSON Schema:
def pydanticToJsonSchema(model: Type[BaseModel]) -> Dict: """Convert Pydantic model to JSON Schema for MCP""" # Use pydantic-to-json-schema library return json_schema(model) -
Keep Workflow Planning:
- Stage 1/Stage 2 planning remains unchanged
- Actions are discovered via MCP tool discovery
- Parameters validated via MCP JSON Schema
Benefits of MCP Integration
1. Standardization
- ✅ Actions become standard MCP tools
- ✅ Consistent interface across all actions
- ✅ Standardized error handling
2. Extensibility
- ✅ Easy to add external MCP servers
- ✅ Third-party tools can be integrated
- ✅ No custom integration code needed
3. Interoperability
- ✅ Compatible with other MCP clients
- ✅ Can be used by external AI systems
- ✅ Standard protocol (JSON-RPC)
4. Tool Discovery
- ✅ Dynamic tool discovery
- ✅ Runtime capability detection
- ✅ No hardcoded action lists
Challenges and Considerations
1. Planning vs Direct Execution
- Challenge: MCP assumes AI directly calls tools, current system uses planning
- Solution: Keep planning phase, use MCP for execution only
2. State Management
- Challenge: MCP is stateless, current system is stateful
- Solution: State management remains in WorkflowProcessor, MCP tools are stateless
3. Type Conversion
- Challenge: Pydantic models ↔ JSON Schema conversion
- Solution: Use existing libraries (pydantic-to-json-schema)
4. Performance
- Challenge: MCP adds JSON-RPC overhead
- Solution: Option 3 (hybrid) - internal actions direct, external via MCP
Dynamic Model Selection with MCP
How MCP Works with Multiple Models
Key Insight: MCP is completely model-agnostic. The protocol standardizes the interface between AI systems and tools, not the AI model itself.
Your Current Architecture:
# Current: Dynamic model selection per call
selectedModel = aiObjects.selectModel(
operationType=OperationTypeEnum.DATA_EXTRACT,
priority=options.priority,
contentType=options.contentType
)
result = await aiObjects.call(request, selectedModel)
With MCP:
# MCP: Model selection happens BEFORE MCP call
selectedModel = aiObjects.selectModel(
operationType=OperationTypeEnum.DATA_EXTRACT,
priority=options.priority,
contentType=options.contentType
)
# MCP tool call (model-agnostic - doesn't care which model)
mcpResult = await mcpClient.callTool(
toolName="ai.process",
arguments={"aiPrompt": "...", "contentParts": [...]},
selectedModel=selectedModel # Passed for logging/tracking, not protocol requirement
)
Important Points:
- ✅ MCP servers don't know which model calls them - they're model-agnostic
- ✅ Model selection happens in your system - before MCP tool call
- ✅ Failover works the same way - if model fails, select next model, retry MCP call
- ✅ Model-aware chunking - happens before MCP call (chunking is your system's concern)
- ✅ MCP just standardizes tool interface - doesn't care about model selection logic
Model Selection Flow with MCP
User Request
↓
WorkflowProcessor
↓
ActionExecutor
↓
[Model Selection Logic - YOUR SYSTEM]
├─> Select model based on:
│ - operationType (DATA_EXTRACT, DOCUMENT_GENERATE, etc.)
│ - priority (high, medium, low)
│ - contentType (text, image, etc.)
│ - Model capabilities (contextLength, maxTokens)
│ - Failover chain (if first model fails)
↓
[MCP Tool Call - MODEL-AGNOSTIC]
├─> Call MCP tool with selected model
├─> MCP server executes tool (doesn't care which model)
└─> Return result
↓
[If Model Fails]
├─> Select next model from failover chain
└─> Retry MCP tool call with new model
Conclusion
Compatibility Assessment
Overall: ✅ Highly Compatible with some architectural adaptations
Key Findings:
- ✅ Actions ≈ MCP Tools: Direct mapping possible
- ✅ Documents ≈ MCP Resources: Similar concepts
- ✅ Dynamic Model Selection: MCP is model-agnostic - works with any model
- ✅ Multi-Provider Support: MCP works with OpenAI, Anthropic, Google, etc.
- ⚠️ Planning Phase: MCP doesn't have planning, but can be kept
- ⚠️ State Management: MCP is stateless, but state can remain in workflow
Recommended Integration
Option 1: MCP as Action Backend (Recommended)
- Expose actions as MCP tools
- Keep workflow planning unchanged
- Enable external MCP server integration
- Maintain type safety
Benefits:
- Standardized action interface
- Easy external tool integration
- Compatible with MCP ecosystem
- Minimal changes to existing architecture
Next Steps:
- Create MCP server wrapper for methods
- Convert Pydantic models to JSON Schema
- Add MCP client to ActionExecutor
- Test with external MCP servers