-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Description
Summary
Add support for Model Context Protocol (MCP) to enable standardized tool calling across AI agents. MCP provides a universal protocol for AI applications to discover and use tools from external systems, making tool integration more consistent and interoperable.
Motivation
The Model Context Protocol (MCP) is becoming a standard for AI tool integration, supported by major AI platforms. Integrating MCP would:
- Enable scala-ai agents to use any MCP-compatible tool without custom integration
- Allow scala-ai tools to be exposed via MCP for use by other AI systems
- Provide a standardized way to handle tool discovery, invocation, and results
- Support both local and remote tool execution via stdio/WebSocket connections
Current State Analysis
The current scala-ai architecture has:
- ✅ Clean
ToolSpec
abstraction for defining tools - ✅ Tool registration at the agent level via
withTools()
- ✅ Tool call request/response flow with
ToolCallRequest
andToolResultMessage
- ✅ Provider-agnostic tool handling
- ❌ Missing: Actual tool execution layer (tools are defined but not executed)
- ❌ Missing: Tool discovery mechanism
- ❌ Missing: Tool registry/executor pattern
Proposed Design
1. Core Components
import wvlet.airframe.rx.Rx
// Tool executor trait (currently missing in scala-ai)
trait ToolExecutor:
def executeToolCall(toolCall: ToolCallRequest): Rx[ToolResultMessage]
def availableTools: Seq[ToolSpec]
// MCP client implementation
class MCPToolExecutor(servers: Seq[MCPServer]) extends ToolExecutor:
def executeToolCall(toolCall: ToolCallRequest): Rx[ToolResultMessage]
def availableTools: Seq[ToolSpec] // Discovered from MCP servers
def discoverTools(): Rx[Seq[ToolSpec]] // Dynamic tool discovery
// MCP server abstraction
trait MCPServer:
def connect(): Rx[MCPConnection]
def listTools(): Rx[Seq[MCPTool]]
def invokeTool(name: String, args: Map[String, Any]): Rx[MCPToolResult]
2. Integration Points
- Extend ChatSession to support tool execution:
trait ToolEnabledChatSession extends ChatSession:
def withToolExecutor(executor: ToolExecutor): ToolEnabledChatSession
def executeToolCalls(toolCalls: Seq[ToolCallRequest]): Rx[Seq[ToolResultMessage]]
- MCP Tool Adapter to convert between formats:
object MCPToolAdapter:
def fromMCPTool(mcpTool: MCPTool): ToolSpec
def toMCPArguments(args: Map[String, Any]): Json
def fromMCPResult(result: MCPToolResult): ToolResultMessage
- Configuration for MCP servers with Airframe retry:
import wvlet.airframe.control.Retry.withJitter
case class MCPConfig(
servers: Seq[MCPServerConfig],
timeout: Duration = 30.seconds,
retryPolicy: RetryContext = withJitter() // Use Airframe's jittering retry
)
case class MCPServerConfig(
name: String,
transport: MCPTransport, // stdio, websocket, http
command: Option[String], // For stdio transport
url: Option[String] // For websocket/http transport
)
Implementation Plan
Phase 1: Tool Execution Layer (Foundation)
- Create
ToolExecutor
trait usingRx[T]
for async operations - Implement basic
LocalToolExecutor
for testing - Extend
ChatSession
to support tool execution - Add tool execution to chat flow
Phase 2: MCP Client Implementation
- Implement MCP JSON-RPC protocol handling
- Create
MCPToolExecutor
with stdio transport support - Implement tool discovery from MCP servers
- Add WebSocket transport support
- Use airframe-control's retry with jitter for resilience
Phase 3: Tool Adaptation
- Create adapters between MCP tools and ToolSpec
- Handle parameter type conversions
- Support structured tool results
- Add output schema validation
Phase 4: Integration & Testing
- Create MCP-enabled agent builder
- Add integration tests with sample MCP servers
- Document MCP usage and configuration
- Create example MCP tool implementations
Example Usage
import wvlet.airframe.control.Retry.withJitter
// Configure MCP servers
val mcpConfig = MCPConfig(
servers = Seq(
MCPServerConfig("filesystem", MCPTransport.Stdio, Some("mcp-server-filesystem")),
MCPServerConfig("slack", MCPTransport.WebSocket, Some("ws://localhost:8080/mcp"))
),
retryPolicy = withJitter(maxRetry = 3)
)
// Create MCP-enabled agent
val agent = LLMAgent("assistant")
.withModel(LLM.Bedrock.Claude3Sonnet)
.withMCPTools(mcpConfig) // Discovers and registers MCP tools
// Tools are automatically executed during chat
val session = agent.newChatSession
val response = session.chat("List all files in the current directory")
// Agent discovers filesystem tool, calls it via MCP, and returns results
Benefits
- Interoperability: Use any MCP-compatible tool without custom integration
- Standardization: Consistent tool calling across different AI systems
- Extensibility: Easy to add new tools via MCP servers
- Security: Built-in human-in-the-loop approval for tool invocations
- Discovery: Dynamic tool discovery at runtime
- Reactive: Uses Airframe's Rx for composable async operations
- Resilient: Built-in retry with jitter for fault tolerance
Technical Notes
- Use
wvlet.airframe.rx.Rx
instead of Scala Future for consistency with Airframe ecosystem - Use
wvlet.airframe.control.Retry.withJitter
for retry logic instead of custom exponential backoff - Leverage existing Airframe components for HTTP, JSON handling, and control flow
References
- MCP Specification
- MCP Tools Documentation
- MCP Introduction
- Airframe Rx Documentation
- Airframe Control Retry
Related Issues
- Support tool_call in AWS Bedrock Chat #109 - Tool calling support (completed for AWS Bedrock)
Metadata
Metadata
Assignees
Labels
No labels