629 lines
18 KiB
Markdown
629 lines
18 KiB
Markdown
# 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
|
|
|
|
1. **MCP Server**: Provides capabilities (tools, resources, prompts) to AI clients
|
|
2. **MCP Client**: AI system that uses MCP servers to access external capabilities
|
|
3. **Tools**: Executable functions that the AI can call (similar to actions)
|
|
4. **Resources**: Readable data sources (files, databases, APIs)
|
|
5. **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 `@action` decorator
|
|
- **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
|
|
|
|
1. **Tool/Action Equivalence**:
|
|
- **MCP Tools** ≈ **Current Actions**
|
|
- Both are executable functions with parameters
|
|
- Both support discovery and execution
|
|
- Both can return results
|
|
|
|
2. **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
|
|
|
|
3. **Type Safety**:
|
|
- **MCP**: Uses JSON Schema for tool parameters
|
|
- **Current**: Uses Pydantic models for action parameters
|
|
- **Compatibility**: Both provide type validation
|
|
|
|
4. **Modularity**:
|
|
- **MCP**: Servers are modular and composable
|
|
- **Current**: Methods are modular and composable
|
|
- **Compatibility**: Both support modular architecture
|
|
|
|
### ⚠️ Incompatible Aspects
|
|
|
|
1. **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
|
|
|
|
2. **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
|
|
|
|
3. **State Management**:
|
|
- **MCP**: Stateless tool calls
|
|
- **Current**: Stateful workflow (rounds, tasks, actions)
|
|
- **Conflict**: MCP doesn't track workflow state
|
|
|
|
4. **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**:
|
|
```python
|
|
# 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**:
|
|
```python
|
|
# 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**:
|
|
```json
|
|
{
|
|
"tools": [
|
|
{
|
|
"name": "read_file",
|
|
"description": "Read a file",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"path": {"type": "string"}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Current**:
|
|
```python
|
|
@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**:
|
|
```json
|
|
{
|
|
"method": "tools/call",
|
|
"params": {
|
|
"name": "read_file",
|
|
"arguments": {"path": "/tmp/file.txt"}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Current**:
|
|
```python
|
|
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**:
|
|
```json
|
|
{
|
|
"resources": [
|
|
{
|
|
"uri": "file:///tmp/document.pdf",
|
|
"name": "Document PDF",
|
|
"mimeType": "application/pdf"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Current**:
|
|
```python
|
|
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**:
|
|
1. **Standardization**: Actions become MCP tools (standard interface)
|
|
2. **Extensibility**: Can add external MCP servers easily
|
|
3. **Compatibility**: Keep existing workflow planning
|
|
4. **Type Safety**: MCP JSON Schema ↔ Pydantic models
|
|
|
|
**Implementation Steps**:
|
|
|
|
1. **Create MCP Server Wrapper**:
|
|
```python
|
|
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
|
|
```
|
|
|
|
2. **Create MCP Client in ActionExecutor**:
|
|
```python
|
|
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)
|
|
```
|
|
|
|
3. **Convert Pydantic to JSON Schema**:
|
|
```python
|
|
def pydanticToJsonSchema(model: Type[BaseModel]) -> Dict:
|
|
"""Convert Pydantic model to JSON Schema for MCP"""
|
|
# Use pydantic-to-json-schema library
|
|
return json_schema(model)
|
|
```
|
|
|
|
4. **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**:
|
|
```python
|
|
# 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**:
|
|
```python
|
|
# 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**:
|
|
1. ✅ **MCP servers don't know which model calls them** - they're model-agnostic
|
|
2. ✅ **Model selection happens in your system** - before MCP tool call
|
|
3. ✅ **Failover works the same way** - if model fails, select next model, retry MCP call
|
|
4. ✅ **Model-aware chunking** - happens before MCP call (chunking is your system's concern)
|
|
5. ✅ **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**:
|
|
1. ✅ **Actions ≈ MCP Tools**: Direct mapping possible
|
|
2. ✅ **Documents ≈ MCP Resources**: Similar concepts
|
|
3. ✅ **Dynamic Model Selection**: MCP is model-agnostic - works with any model
|
|
4. ✅ **Multi-Provider Support**: MCP works with OpenAI, Anthropic, Google, etc.
|
|
5. ⚠️ **Planning Phase**: MCP doesn't have planning, but can be kept
|
|
6. ⚠️ **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**:
|
|
1. Create MCP server wrapper for methods
|
|
2. Convert Pydantic models to JSON Schema
|
|
3. Add MCP client to ActionExecutor
|
|
4. Test with external MCP servers
|
|
|