Fix inbox creation issues in agent coordinator
- Fixed Task.new/3 to handle both maps and keyword lists - Added robust inbox existence checking in find_available_agent - Ensure inbox creation during agent registration and task assignment - Add helper function ensure_inbox_exists to avoid crashes
This commit is contained in:
226
examples/auto_heartbeat_demo.exs
Normal file
226
examples/auto_heartbeat_demo.exs
Normal file
@@ -0,0 +1,226 @@
|
||||
#!/usr/bin/env elixir
|
||||
|
||||
# Auto-heartbeat demo script
|
||||
# This demonstrates the enhanced coordination system with automatic heartbeats
|
||||
|
||||
Mix.install([
|
||||
{:jason, "~> 1.4"},
|
||||
{:uuid, "~> 1.1"}
|
||||
])
|
||||
|
||||
# Load the agent coordinator modules
|
||||
Code.require_file("lib/agent_coordinator.ex")
|
||||
Code.require_file("lib/agent_coordinator/agent.ex")
|
||||
Code.require_file("lib/agent_coordinator/task.ex")
|
||||
Code.require_file("lib/agent_coordinator/inbox.ex")
|
||||
Code.require_file("lib/agent_coordinator/task_registry.ex")
|
||||
Code.require_file("lib/agent_coordinator/mcp_server.ex")
|
||||
Code.require_file("lib/agent_coordinator/auto_heartbeat.ex")
|
||||
Code.require_file("lib/agent_coordinator/enhanced_mcp_server.ex")
|
||||
Code.require_file("lib/agent_coordinator/client.ex")
|
||||
|
||||
defmodule AutoHeartbeatDemo do
|
||||
@moduledoc """
|
||||
Demonstrates the automatic heartbeat functionality
|
||||
"""
|
||||
|
||||
def run do
|
||||
IO.puts("🚀 Starting Auto-Heartbeat Demo")
|
||||
IO.puts("================================")
|
||||
|
||||
# Start the core services
|
||||
start_services()
|
||||
|
||||
# Demo 1: Basic client with auto-heartbeat
|
||||
demo_basic_client()
|
||||
|
||||
# Demo 2: Multiple agents with coordination
|
||||
demo_multiple_agents()
|
||||
|
||||
# Demo 3: Task creation and completion with heartbeats
|
||||
demo_task_workflow()
|
||||
|
||||
IO.puts("\n✅ Demo completed!")
|
||||
end
|
||||
|
||||
defp start_services do
|
||||
IO.puts("\n📡 Starting coordination services...")
|
||||
|
||||
# Start registry for inboxes
|
||||
Registry.start_link(keys: :unique, name: AgentCoordinator.InboxRegistry)
|
||||
|
||||
# Start dynamic supervisor
|
||||
DynamicSupervisor.start_link(name: AgentCoordinator.InboxSupervisor, strategy: :one_for_one)
|
||||
|
||||
# Start task registry (without NATS for demo)
|
||||
AgentCoordinator.TaskRegistry.start_link()
|
||||
|
||||
# Start MCP servers
|
||||
AgentCoordinator.MCPServer.start_link()
|
||||
AgentCoordinator.AutoHeartbeat.start_link()
|
||||
AgentCoordinator.EnhancedMCPServer.start_link()
|
||||
|
||||
Process.sleep(500) # Let services initialize
|
||||
IO.puts("✅ Services started")
|
||||
end
|
||||
|
||||
defp demo_basic_client do
|
||||
IO.puts("\n🤖 Demo 1: Basic Client with Auto-Heartbeat")
|
||||
IO.puts("-------------------------------------------")
|
||||
|
||||
# Start a client session
|
||||
{:ok, client} = AgentCoordinator.Client.start_session(
|
||||
"DemoAgent1",
|
||||
[:coding, :analysis],
|
||||
auto_heartbeat: true,
|
||||
heartbeat_interval: 3000 # 3 seconds for demo
|
||||
)
|
||||
|
||||
# Get session info
|
||||
{:ok, info} = AgentCoordinator.Client.get_session_info(client)
|
||||
IO.puts("Agent registered: #{info.agent_name} (ID: #{info.agent_id})")
|
||||
IO.puts("Auto-heartbeat enabled: #{info.auto_heartbeat_enabled}")
|
||||
|
||||
# Check task board to see the agent
|
||||
{:ok, board} = AgentCoordinator.Client.get_task_board(client)
|
||||
agent = Enum.find(board.agents, fn a -> a["agent_id"] == info.agent_id end)
|
||||
|
||||
IO.puts("Agent status: #{agent["status"]}")
|
||||
IO.puts("Agent online: #{agent["online"]}")
|
||||
IO.puts("Session active: #{agent["session_active"]}")
|
||||
|
||||
# Wait and check heartbeat activity
|
||||
IO.puts("\n⏱️ Waiting 8 seconds to observe automatic heartbeats...")
|
||||
Process.sleep(8000)
|
||||
|
||||
# Check board again
|
||||
{:ok, updated_board} = AgentCoordinator.Client.get_task_board(client)
|
||||
updated_agent = Enum.find(updated_board.agents, fn a -> a["agent_id"] == info.agent_id end)
|
||||
|
||||
IO.puts("Agent still online: #{updated_agent["online"]}")
|
||||
IO.puts("Active sessions: #{updated_board.active_sessions}")
|
||||
|
||||
# Stop the client
|
||||
AgentCoordinator.Client.stop_session(client)
|
||||
IO.puts("✅ Client session stopped")
|
||||
end
|
||||
|
||||
defp demo_multiple_agents do
|
||||
IO.puts("\n👥 Demo 2: Multiple Agents Coordination")
|
||||
IO.puts("--------------------------------------")
|
||||
|
||||
# Start multiple agents
|
||||
agents = []
|
||||
|
||||
{:ok, agent1} = AgentCoordinator.Client.start_session("CodingAgent", [:coding, :testing])
|
||||
{:ok, agent2} = AgentCoordinator.Client.start_session("AnalysisAgent", [:analysis, :documentation])
|
||||
{:ok, agent3} = AgentCoordinator.Client.start_session("ReviewAgent", [:review, :analysis])
|
||||
|
||||
agents = [agent1, agent2, agent3]
|
||||
|
||||
# Check the task board
|
||||
{:ok, board} = AgentCoordinator.Client.get_task_board(agent1)
|
||||
IO.puts("Total agents: #{length(board.agents)}")
|
||||
IO.puts("Active sessions: #{board.active_sessions}")
|
||||
|
||||
Enum.each(board.agents, fn agent ->
|
||||
if agent["online"] do
|
||||
IO.puts(" - #{agent["name"]}: #{Enum.join(agent["capabilities"], ", ")} (ONLINE)")
|
||||
else
|
||||
IO.puts(" - #{agent["name"]}: #{Enum.join(agent["capabilities"], ", ")} (offline)")
|
||||
end
|
||||
end)
|
||||
|
||||
# Demonstrate heartbeat coordination
|
||||
IO.puts("\n💓 All agents sending heartbeats...")
|
||||
|
||||
# Each agent does some activity
|
||||
Enum.each(agents, fn agent ->
|
||||
AgentCoordinator.Client.heartbeat(agent)
|
||||
end)
|
||||
|
||||
Process.sleep(1000)
|
||||
|
||||
# Check board after activity
|
||||
{:ok, updated_board} = AgentCoordinator.Client.get_task_board(agent1)
|
||||
online_count = Enum.count(updated_board.agents, fn a -> a["online"] end)
|
||||
IO.puts("Agents online after heartbeat activity: #{online_count}/#{length(updated_board.agents)}")
|
||||
|
||||
# Cleanup
|
||||
Enum.each(agents, &AgentCoordinator.Client.stop_session/1)
|
||||
IO.puts("✅ All agents disconnected")
|
||||
end
|
||||
|
||||
defp demo_task_workflow do
|
||||
IO.puts("\n📋 Demo 3: Task Workflow with Heartbeats")
|
||||
IO.puts("---------------------------------------")
|
||||
|
||||
# Start an agent
|
||||
{:ok, agent} = AgentCoordinator.Client.start_session("WorkflowAgent", [:coding, :testing])
|
||||
|
||||
# Create a task
|
||||
task_result = AgentCoordinator.Client.create_task(
|
||||
agent,
|
||||
"Fix Bug #123",
|
||||
"Fix the authentication bug in user login",
|
||||
%{
|
||||
"priority" => "high",
|
||||
"file_paths" => ["lib/auth.ex", "test/auth_test.exs"],
|
||||
"required_capabilities" => ["coding", "testing"]
|
||||
}
|
||||
)
|
||||
|
||||
case task_result do
|
||||
{:ok, task_data} ->
|
||||
IO.puts("✅ Task created: #{task_data["task_id"]}")
|
||||
|
||||
# Check heartbeat metadata
|
||||
if Map.has_key?(task_data, "_heartbeat_metadata") do
|
||||
metadata = task_data["_heartbeat_metadata"]
|
||||
IO.puts(" Heartbeat metadata: Agent #{metadata["agent_id"]} at #{metadata["timestamp"]}")
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
IO.puts("❌ Task creation failed: #{reason}")
|
||||
end
|
||||
|
||||
# Try to get next task
|
||||
case AgentCoordinator.Client.get_next_task(agent) do
|
||||
{:ok, task} ->
|
||||
if Map.has_key?(task, "task_id") do
|
||||
IO.puts("📝 Got task: #{task["title"]}")
|
||||
|
||||
# Simulate some work
|
||||
IO.puts("⚙️ Working on task...")
|
||||
Process.sleep(2000)
|
||||
|
||||
# Complete the task
|
||||
case AgentCoordinator.Client.complete_task(agent) do
|
||||
{:ok, result} ->
|
||||
IO.puts("✅ Task completed: #{result["task_id"]}")
|
||||
|
||||
{:error, reason} ->
|
||||
IO.puts("❌ Task completion failed: #{reason}")
|
||||
end
|
||||
else
|
||||
IO.puts("📝 No tasks available: #{task["message"]}")
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
IO.puts("❌ Failed to get task: #{reason}")
|
||||
end
|
||||
|
||||
# Final status check
|
||||
{:ok, final_info} = AgentCoordinator.Client.get_session_info(agent)
|
||||
IO.puts("Final session info:")
|
||||
IO.puts(" - Last heartbeat: #{final_info.last_heartbeat}")
|
||||
IO.puts(" - Session duration: #{final_info.session_duration} seconds")
|
||||
|
||||
# Cleanup
|
||||
AgentCoordinator.Client.stop_session(agent)
|
||||
IO.puts("✅ Workflow demo completed")
|
||||
end
|
||||
end
|
||||
|
||||
# Run the demo
|
||||
AutoHeartbeatDemo.run()
|
||||
150
examples/demo_mcp_server.exs
Normal file
150
examples/demo_mcp_server.exs
Normal file
@@ -0,0 +1,150 @@
|
||||
defmodule MCPServerDemo do
|
||||
@moduledoc """
|
||||
Demonstration script showing MCP server functionality
|
||||
"""
|
||||
|
||||
alias AgentCoordinator.MCPServer
|
||||
|
||||
def run do
|
||||
IO.puts("🚀 Testing Agent Coordinator MCP Server")
|
||||
IO.puts("=" |> String.duplicate(50))
|
||||
|
||||
# Test 1: Get tools list
|
||||
IO.puts("\n📋 Getting available tools...")
|
||||
tools_request = %{"method" => "tools/list", "jsonrpc" => "2.0", "id" => 1}
|
||||
tools_response = MCPServer.handle_mcp_request(tools_request)
|
||||
|
||||
case tools_response do
|
||||
%{"result" => %{"tools" => tools}} ->
|
||||
IO.puts("✅ Found #{length(tools)} tools:")
|
||||
Enum.each(tools, fn tool ->
|
||||
IO.puts(" - #{tool["name"]}: #{tool["description"]}")
|
||||
end)
|
||||
error ->
|
||||
IO.puts("❌ Error getting tools: #{inspect(error)}")
|
||||
end
|
||||
|
||||
# Test 2: Register an agent
|
||||
IO.puts("\n👤 Registering test agent...")
|
||||
register_request = %{
|
||||
"method" => "tools/call",
|
||||
"params" => %{
|
||||
"name" => "register_agent",
|
||||
"arguments" => %{
|
||||
"name" => "DemoAgent",
|
||||
"capabilities" => ["coding", "testing"]
|
||||
}
|
||||
},
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => 2
|
||||
}
|
||||
|
||||
register_response = MCPServer.handle_mcp_request(register_request)
|
||||
|
||||
agent_id = case register_response do
|
||||
%{"result" => %{"content" => [%{"text" => text}]}} ->
|
||||
data = Jason.decode!(text)
|
||||
IO.puts("✅ Agent registered: #{data["agent_id"]}")
|
||||
data["agent_id"]
|
||||
error ->
|
||||
IO.puts("❌ Error registering agent: #{inspect(error)}")
|
||||
nil
|
||||
end
|
||||
|
||||
if agent_id do
|
||||
# Test 3: Create a task
|
||||
IO.puts("\n📝 Creating a test task...")
|
||||
task_request = %{
|
||||
"method" => "tools/call",
|
||||
"params" => %{
|
||||
"name" => "create_task",
|
||||
"arguments" => %{
|
||||
"title" => "Demo Task",
|
||||
"description" => "A demonstration task for the MCP server",
|
||||
"priority" => "high",
|
||||
"required_capabilities" => ["coding"]
|
||||
}
|
||||
},
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => 3
|
||||
}
|
||||
|
||||
task_response = MCPServer.handle_mcp_request(task_request)
|
||||
|
||||
case task_response do
|
||||
%{"result" => %{"content" => [%{"text" => text}]}} ->
|
||||
data = Jason.decode!(text)
|
||||
IO.puts("✅ Task created: #{data["task_id"]}")
|
||||
if data["assigned_to"] do
|
||||
IO.puts(" Assigned to: #{data["assigned_to"]}")
|
||||
end
|
||||
error ->
|
||||
IO.puts("❌ Error creating task: #{inspect(error)}")
|
||||
end
|
||||
|
||||
# Test 4: Get task board
|
||||
IO.puts("\n📊 Getting task board...")
|
||||
board_request = %{
|
||||
"method" => "tools/call",
|
||||
"params" => %{
|
||||
"name" => "get_task_board",
|
||||
"arguments" => %{}
|
||||
},
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => 4
|
||||
}
|
||||
|
||||
board_response = MCPServer.handle_mcp_request(board_request)
|
||||
|
||||
case board_response do
|
||||
%{"result" => %{"content" => [%{"text" => text}]}} ->
|
||||
data = Jason.decode!(text)
|
||||
IO.puts("✅ Task board retrieved:")
|
||||
Enum.each(data["agents"], fn agent ->
|
||||
IO.puts(" Agent: #{agent["name"]} (#{agent["agent_id"]})")
|
||||
IO.puts(" Capabilities: #{Enum.join(agent["capabilities"], ", ")}")
|
||||
IO.puts(" Status: #{agent["status"]}")
|
||||
if agent["current_task"] do
|
||||
IO.puts(" Current Task: #{agent["current_task"]["title"]}")
|
||||
else
|
||||
IO.puts(" Current Task: None")
|
||||
end
|
||||
IO.puts(" Pending: #{agent["pending_tasks"]} | Completed: #{agent["completed_tasks"]}")
|
||||
IO.puts("")
|
||||
end)
|
||||
error ->
|
||||
IO.puts("❌ Error getting task board: #{inspect(error)}")
|
||||
end
|
||||
|
||||
# Test 5: Send heartbeat
|
||||
IO.puts("\n💓 Sending heartbeat...")
|
||||
heartbeat_request = %{
|
||||
"method" => "tools/call",
|
||||
"params" => %{
|
||||
"name" => "heartbeat",
|
||||
"arguments" => %{
|
||||
"agent_id" => agent_id
|
||||
}
|
||||
},
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => 5
|
||||
}
|
||||
|
||||
heartbeat_response = MCPServer.handle_mcp_request(heartbeat_request)
|
||||
|
||||
case heartbeat_response do
|
||||
%{"result" => %{"content" => [%{"text" => text}]}} ->
|
||||
data = Jason.decode!(text)
|
||||
IO.puts("✅ Heartbeat sent: #{data["status"]}")
|
||||
error ->
|
||||
IO.puts("❌ Error sending heartbeat: #{inspect(error)}")
|
||||
end
|
||||
end
|
||||
|
||||
IO.puts("\n🎉 MCP Server testing completed!")
|
||||
IO.puts("=" |> String.duplicate(50))
|
||||
end
|
||||
end
|
||||
|
||||
# Run the demo
|
||||
MCPServerDemo.run()
|
||||
172
examples/full_workflow_demo.exs
Normal file
172
examples/full_workflow_demo.exs
Normal file
@@ -0,0 +1,172 @@
|
||||
defmodule FullWorkflowDemo do
|
||||
@moduledoc """
|
||||
Demonstration of the complete task workflow
|
||||
"""
|
||||
|
||||
alias AgentCoordinator.MCPServer
|
||||
|
||||
def run do
|
||||
IO.puts("🚀 Complete Agent Coordinator Workflow Demo")
|
||||
IO.puts("=" |> String.duplicate(50))
|
||||
|
||||
# Register multiple agents
|
||||
IO.puts("\n👥 Registering multiple agents...")
|
||||
|
||||
agents = [
|
||||
%{"name" => "CodingAgent", "capabilities" => ["coding", "debugging"]},
|
||||
%{"name" => "TestingAgent", "capabilities" => ["testing", "qa"]},
|
||||
%{"name" => "FullStackAgent", "capabilities" => ["coding", "testing", "ui"]}
|
||||
]
|
||||
|
||||
agent_ids = Enum.map(agents, fn agent ->
|
||||
register_request = %{
|
||||
"method" => "tools/call",
|
||||
"params" => %{
|
||||
"name" => "register_agent",
|
||||
"arguments" => agent
|
||||
},
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => :rand.uniform(1000)
|
||||
}
|
||||
|
||||
case MCPServer.handle_mcp_request(register_request) do
|
||||
%{"result" => %{"content" => [%{"text" => text}]}} ->
|
||||
data = Jason.decode!(text)
|
||||
IO.puts("✅ #{agent["name"]} registered: #{data["agent_id"]}")
|
||||
data["agent_id"]
|
||||
error ->
|
||||
IO.puts("❌ Error registering #{agent["name"]}: #{inspect(error)}")
|
||||
nil
|
||||
end
|
||||
end)
|
||||
|
||||
# Create tasks with different requirements
|
||||
IO.puts("\n📝 Creating various tasks...")
|
||||
|
||||
tasks = [
|
||||
%{"title" => "Fix Bug #123", "description" => "Debug authentication issue", "priority" => "high", "required_capabilities" => ["coding", "debugging"]},
|
||||
%{"title" => "Write Unit Tests", "description" => "Create comprehensive test suite", "priority" => "medium", "required_capabilities" => ["testing"]},
|
||||
%{"title" => "UI Enhancement", "description" => "Improve user interface", "priority" => "low", "required_capabilities" => ["ui", "coding"]},
|
||||
%{"title" => "Code Review", "description" => "Review pull request #456", "priority" => "medium", "required_capabilities" => ["coding"]}
|
||||
]
|
||||
|
||||
task_ids = Enum.map(tasks, fn task ->
|
||||
task_request = %{
|
||||
"method" => "tools/call",
|
||||
"params" => %{
|
||||
"name" => "create_task",
|
||||
"arguments" => task
|
||||
},
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => :rand.uniform(1000)
|
||||
}
|
||||
|
||||
case MCPServer.handle_mcp_request(task_request) do
|
||||
%{"result" => %{"content" => [%{"text" => text}]}} ->
|
||||
data = Jason.decode!(text)
|
||||
IO.puts("✅ Task '#{task["title"]}' created: #{data["task_id"]}")
|
||||
if data["assigned_to"] do
|
||||
IO.puts(" → Assigned to: #{data["assigned_to"]}")
|
||||
end
|
||||
data["task_id"]
|
||||
error ->
|
||||
IO.puts("❌ Error creating task '#{task["title"]}': #{inspect(error)}")
|
||||
nil
|
||||
end
|
||||
end)
|
||||
|
||||
# Show current task board
|
||||
IO.puts("\n📊 Current Task Board:")
|
||||
show_task_board()
|
||||
|
||||
# Test getting next task for first agent
|
||||
if agent_id = Enum.at(agent_ids, 0) do
|
||||
IO.puts("\n🎯 Getting next task for CodingAgent...")
|
||||
next_task_request = %{
|
||||
"method" => "tools/call",
|
||||
"params" => %{
|
||||
"name" => "get_next_task",
|
||||
"arguments" => %{
|
||||
"agent_id" => agent_id
|
||||
}
|
||||
},
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => :rand.uniform(1000)
|
||||
}
|
||||
|
||||
case MCPServer.handle_mcp_request(next_task_request) do
|
||||
%{"result" => %{"content" => [%{"text" => text}]}} ->
|
||||
data = Jason.decode!(text)
|
||||
if data["task"] do
|
||||
IO.puts("✅ Got task: #{data["task"]["title"]}")
|
||||
|
||||
# Complete the task
|
||||
IO.puts("\n✅ Completing the task...")
|
||||
complete_request = %{
|
||||
"method" => "tools/call",
|
||||
"params" => %{
|
||||
"name" => "complete_task",
|
||||
"arguments" => %{
|
||||
"agent_id" => agent_id,
|
||||
"result" => "Task completed successfully!"
|
||||
}
|
||||
},
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => :rand.uniform(1000)
|
||||
}
|
||||
|
||||
case MCPServer.handle_mcp_request(complete_request) do
|
||||
%{"result" => %{"content" => [%{"text" => text}]}} ->
|
||||
completion_data = Jason.decode!(text)
|
||||
IO.puts("✅ Task completed: #{completion_data["message"]}")
|
||||
error ->
|
||||
IO.puts("❌ Error completing task: #{inspect(error)}")
|
||||
end
|
||||
else
|
||||
IO.puts("ℹ️ No tasks available: #{data["message"]}")
|
||||
end
|
||||
error ->
|
||||
IO.puts("❌ Error getting next task: #{inspect(error)}")
|
||||
end
|
||||
end
|
||||
|
||||
# Final task board
|
||||
IO.puts("\n📊 Final Task Board:")
|
||||
show_task_board()
|
||||
|
||||
IO.puts("\n🎉 Complete workflow demonstration finished!")
|
||||
IO.puts("=" |> String.duplicate(50))
|
||||
end
|
||||
|
||||
defp show_task_board do
|
||||
board_request = %{
|
||||
"method" => "tools/call",
|
||||
"params" => %{
|
||||
"name" => "get_task_board",
|
||||
"arguments" => %{}
|
||||
},
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => :rand.uniform(1000)
|
||||
}
|
||||
|
||||
case MCPServer.handle_mcp_request(board_request) do
|
||||
%{"result" => %{"content" => [%{"text" => text}]}} ->
|
||||
data = Jason.decode!(text)
|
||||
Enum.each(data["agents"], fn agent ->
|
||||
IO.puts(" 📱 #{agent["name"]} (#{String.slice(agent["agent_id"], 0, 8)}...)")
|
||||
IO.puts(" Capabilities: #{Enum.join(agent["capabilities"], ", ")}")
|
||||
IO.puts(" Status: #{agent["status"]}")
|
||||
if agent["current_task"] do
|
||||
IO.puts(" 🎯 Current: #{agent["current_task"]["title"]}")
|
||||
end
|
||||
IO.puts(" 📈 Stats: #{agent["pending_tasks"]} pending | #{agent["completed_tasks"]} completed")
|
||||
IO.puts("")
|
||||
end)
|
||||
error ->
|
||||
IO.puts("❌ Error getting task board: #{inspect(error)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Run the demo
|
||||
FullWorkflowDemo.run()
|
||||
193
examples/mcp_client_example.py
Executable file
193
examples/mcp_client_example.py
Executable file
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
AgentCoordinator MCP Client Example
|
||||
|
||||
This script demonstrates how to connect to and interact with the
|
||||
AgentCoordinator MCP server programmatically.
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import uuid
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
class AgentCoordinatorMCP:
|
||||
def __init__(self, launcher_path: str = "./scripts/mcp_launcher.sh"):
|
||||
self.launcher_path = launcher_path
|
||||
self.process = None
|
||||
|
||||
def start(self):
|
||||
"""Start the MCP server process"""
|
||||
try:
|
||||
self.process = subprocess.Popen(
|
||||
[self.launcher_path],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
bufsize=0
|
||||
)
|
||||
print("🚀 MCP server started")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to start MCP server: {e}")
|
||||
return False
|
||||
|
||||
def stop(self):
|
||||
"""Stop the MCP server process"""
|
||||
if self.process:
|
||||
self.process.terminate()
|
||||
self.process.wait()
|
||||
print("🛑 MCP server stopped")
|
||||
|
||||
def send_request(self, method: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""Send a JSON-RPC request to the MCP server"""
|
||||
if not self.process:
|
||||
raise RuntimeError("MCP server not started")
|
||||
|
||||
request = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": str(uuid.uuid4()),
|
||||
"method": method
|
||||
}
|
||||
|
||||
if params:
|
||||
request["params"] = params
|
||||
|
||||
# Send request
|
||||
request_json = json.dumps(request) + "\n"
|
||||
self.process.stdin.write(request_json)
|
||||
self.process.stdin.flush()
|
||||
|
||||
# Read response
|
||||
response_line = self.process.stdout.readline()
|
||||
if not response_line:
|
||||
raise RuntimeError("No response from MCP server")
|
||||
|
||||
return json.loads(response_line.strip())
|
||||
|
||||
def get_tools(self) -> Dict[str, Any]:
|
||||
"""Get list of available tools"""
|
||||
return self.send_request("tools/list")
|
||||
|
||||
def register_agent(self, name: str, capabilities: list) -> Dict[str, Any]:
|
||||
"""Register a new agent"""
|
||||
return self.send_request("tools/call", {
|
||||
"name": "register_agent",
|
||||
"arguments": {
|
||||
"name": name,
|
||||
"capabilities": capabilities
|
||||
}
|
||||
})
|
||||
|
||||
def create_task(self, title: str, description: str, priority: str = "normal",
|
||||
required_capabilities: list = None) -> Dict[str, Any]:
|
||||
"""Create a new task"""
|
||||
args = {
|
||||
"title": title,
|
||||
"description": description,
|
||||
"priority": priority
|
||||
}
|
||||
if required_capabilities:
|
||||
args["required_capabilities"] = required_capabilities
|
||||
|
||||
return self.send_request("tools/call", {
|
||||
"name": "create_task",
|
||||
"arguments": args
|
||||
})
|
||||
|
||||
def get_next_task(self, agent_id: str) -> Dict[str, Any]:
|
||||
"""Get next task for an agent"""
|
||||
return self.send_request("tools/call", {
|
||||
"name": "get_next_task",
|
||||
"arguments": {"agent_id": agent_id}
|
||||
})
|
||||
|
||||
def complete_task(self, agent_id: str, result: str) -> Dict[str, Any]:
|
||||
"""Complete current task"""
|
||||
return self.send_request("tools/call", {
|
||||
"name": "complete_task",
|
||||
"arguments": {
|
||||
"agent_id": agent_id,
|
||||
"result": result
|
||||
}
|
||||
})
|
||||
|
||||
def get_task_board(self) -> Dict[str, Any]:
|
||||
"""Get task board overview"""
|
||||
return self.send_request("tools/call", {
|
||||
"name": "get_task_board",
|
||||
"arguments": {}
|
||||
})
|
||||
|
||||
def heartbeat(self, agent_id: str) -> Dict[str, Any]:
|
||||
"""Send agent heartbeat"""
|
||||
return self.send_request("tools/call", {
|
||||
"name": "heartbeat",
|
||||
"arguments": {"agent_id": agent_id}
|
||||
})
|
||||
|
||||
def demo():
|
||||
"""Demonstrate MCP client functionality"""
|
||||
print("🎯 AgentCoordinator MCP Client Demo")
|
||||
print("=" * 50)
|
||||
|
||||
client = AgentCoordinatorMCP()
|
||||
|
||||
try:
|
||||
# Start server
|
||||
if not client.start():
|
||||
return
|
||||
|
||||
# Wait for server to be ready
|
||||
import time
|
||||
time.sleep(2)
|
||||
|
||||
# Get tools
|
||||
print("\n📋 Available tools:")
|
||||
tools_response = client.get_tools()
|
||||
if "result" in tools_response:
|
||||
for tool in tools_response["result"]["tools"]:
|
||||
print(f" - {tool['name']}: {tool['description']}")
|
||||
|
||||
# Register agent
|
||||
print("\n👤 Registering agent...")
|
||||
register_response = client.register_agent("PythonAgent", ["coding", "testing"])
|
||||
if "result" in register_response:
|
||||
content = register_response["result"]["content"][0]["text"]
|
||||
agent_data = json.loads(content)
|
||||
agent_id = agent_data["agent_id"]
|
||||
print(f"✅ Agent registered: {agent_id}")
|
||||
|
||||
# Create task
|
||||
print("\n📝 Creating task...")
|
||||
task_response = client.create_task(
|
||||
"Python Script",
|
||||
"Write a Python script for data processing",
|
||||
"high",
|
||||
["coding"]
|
||||
)
|
||||
if "result" in task_response:
|
||||
content = task_response["result"]["content"][0]["text"]
|
||||
task_data = json.loads(content)
|
||||
print(f"✅ Task created: {task_data['task_id']}")
|
||||
|
||||
# Get task board
|
||||
print("\n📊 Task board:")
|
||||
board_response = client.get_task_board()
|
||||
if "result" in board_response:
|
||||
content = board_response["result"]["content"][0]["text"]
|
||||
board_data = json.loads(content)
|
||||
for agent in board_data["agents"]:
|
||||
print(f" 📱 {agent['name']}: {agent['status']}")
|
||||
print(f" Capabilities: {', '.join(agent['capabilities'])}")
|
||||
print(f" Pending: {agent['pending_tasks']}, Completed: {agent['completed_tasks']}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
finally:
|
||||
client.stop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo()
|
||||
235
examples/unified_demo.exs
Normal file
235
examples/unified_demo.exs
Normal file
@@ -0,0 +1,235 @@
|
||||
#!/usr/bin/env elixir
|
||||
|
||||
# Unified MCP Server Demo
|
||||
# This demo shows how the unified MCP server provides automatic task tracking
|
||||
# for all external MCP server operations
|
||||
|
||||
Mix.install([
|
||||
{:agent_coordinator, path: "."},
|
||||
{:jason, "~> 1.4"}
|
||||
])
|
||||
|
||||
defmodule UnifiedDemo do
|
||||
@moduledoc """
|
||||
Demo showing the unified MCP server with automatic task tracking
|
||||
"""
|
||||
|
||||
def run do
|
||||
IO.puts("🚀 Starting Unified MCP Server Demo...")
|
||||
IO.puts("=" * 60)
|
||||
|
||||
# Start the unified system
|
||||
{:ok, _} = AgentCoordinator.TaskRegistry.start_link()
|
||||
{:ok, _} = AgentCoordinator.MCPServerManager.start_link(config_file: "mcp_servers.json")
|
||||
{:ok, _} = AgentCoordinator.UnifiedMCPServer.start_link()
|
||||
|
||||
IO.puts("✅ Unified MCP server started successfully")
|
||||
|
||||
# Demonstrate automatic tool aggregation
|
||||
demonstrate_tool_aggregation()
|
||||
|
||||
# Demonstrate automatic task tracking
|
||||
demonstrate_automatic_task_tracking()
|
||||
|
||||
# Demonstrate coordination features
|
||||
demonstrate_coordination_features()
|
||||
|
||||
IO.puts("\n🎉 Demo completed successfully!")
|
||||
IO.puts("📋 Key Points:")
|
||||
IO.puts(" • All external MCP servers are managed internally")
|
||||
IO.puts(" • Every tool call automatically creates/updates tasks")
|
||||
IO.puts(" • GitHub Copilot sees only one MCP server")
|
||||
IO.puts(" • Coordination tools are still available for planning")
|
||||
end
|
||||
|
||||
defp demonstrate_tool_aggregation do
|
||||
IO.puts("\n📊 Testing Tool Aggregation...")
|
||||
|
||||
# Get all available tools from the unified server
|
||||
request = %{
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => 1,
|
||||
"method" => "tools/list"
|
||||
}
|
||||
|
||||
response = AgentCoordinator.UnifiedMCPServer.handle_mcp_request(request)
|
||||
|
||||
case response do
|
||||
%{"result" => %{"tools" => tools}} ->
|
||||
IO.puts("✅ Found #{length(tools)} total tools from all servers:")
|
||||
|
||||
# Group tools by server origin
|
||||
coordinator_tools =
|
||||
Enum.filter(tools, fn tool ->
|
||||
tool["name"] in ~w[register_agent create_task get_next_task complete_task get_task_board heartbeat]
|
||||
end)
|
||||
|
||||
external_tools = tools -- coordinator_tools
|
||||
|
||||
IO.puts(" • Agent Coordinator: #{length(coordinator_tools)} tools")
|
||||
IO.puts(" • External Servers: #{length(external_tools)} tools")
|
||||
|
||||
# Show sample tools
|
||||
IO.puts("\n📝 Sample Agent Coordinator tools:")
|
||||
|
||||
Enum.take(coordinator_tools, 3)
|
||||
|> Enum.each(fn tool ->
|
||||
IO.puts(" - #{tool["name"]}: #{tool["description"]}")
|
||||
end)
|
||||
|
||||
if length(external_tools) > 0 do
|
||||
IO.puts("\n📝 Sample External tools:")
|
||||
|
||||
Enum.take(external_tools, 3)
|
||||
|> Enum.each(fn tool ->
|
||||
IO.puts(
|
||||
" - #{tool["name"]}: #{String.slice(tool["description"] || "External tool", 0, 50)}"
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
error ->
|
||||
IO.puts("❌ Error getting tools: #{inspect(error)}")
|
||||
end
|
||||
end
|
||||
|
||||
defp demonstrate_automatic_task_tracking do
|
||||
IO.puts("\n🎯 Testing Automatic Task Tracking...")
|
||||
|
||||
# First, register an agent (this creates an agent context)
|
||||
register_request = %{
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => 2,
|
||||
"method" => "tools/call",
|
||||
"params" => %{
|
||||
"name" => "register_agent",
|
||||
"arguments" => %{
|
||||
"name" => "Demo Agent",
|
||||
"capabilities" => ["coding", "analysis"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response = AgentCoordinator.UnifiedMCPServer.handle_mcp_request(register_request)
|
||||
IO.puts("✅ Agent registered: #{inspect(response["result"])}")
|
||||
|
||||
# Now simulate using an external tool - this should automatically create a task
|
||||
# Note: In a real scenario, external servers would be running
|
||||
external_tool_request = %{
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => 3,
|
||||
"method" => "tools/call",
|
||||
"params" => %{
|
||||
"name" => "mcp_filesystem_read_file",
|
||||
"arguments" => %{
|
||||
"path" => "/home/ra/agent_coordinator/README.md"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IO.puts("🔄 Simulating external tool call: mcp_filesystem_read_file")
|
||||
|
||||
external_response =
|
||||
AgentCoordinator.UnifiedMCPServer.handle_mcp_request(external_tool_request)
|
||||
|
||||
case external_response do
|
||||
%{"result" => result} ->
|
||||
IO.puts("✅ Tool call succeeded with automatic task tracking")
|
||||
|
||||
if metadata = result["_metadata"] do
|
||||
IO.puts("📊 Automatic metadata:")
|
||||
IO.puts(" - Tool: #{metadata["tool_name"]}")
|
||||
IO.puts(" - Agent: #{metadata["agent_id"]}")
|
||||
IO.puts(" - Auto-tracked: #{metadata["auto_tracked"]}")
|
||||
end
|
||||
|
||||
%{"error" => error} ->
|
||||
IO.puts("ℹ️ External server not available (expected in demo): #{error["message"]}")
|
||||
IO.puts(" In real usage, this would automatically create a task")
|
||||
end
|
||||
|
||||
# Check the task board to see auto-created tasks
|
||||
IO.puts("\n📋 Checking Task Board...")
|
||||
|
||||
task_board_request = %{
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => 4,
|
||||
"method" => "tools/call",
|
||||
"params" => %{
|
||||
"name" => "get_task_board",
|
||||
"arguments" => %{}
|
||||
}
|
||||
}
|
||||
|
||||
board_response = AgentCoordinator.UnifiedMCPServer.handle_mcp_request(task_board_request)
|
||||
|
||||
case board_response do
|
||||
%{"result" => %{"content" => [%{"text" => board_json}]}} ->
|
||||
case Jason.decode(board_json) do
|
||||
{:ok, board} ->
|
||||
IO.puts("✅ Task Board Status:")
|
||||
IO.puts(" - Total Agents: #{board["total_agents"]}")
|
||||
IO.puts(" - Active Tasks: #{board["active_tasks"]}")
|
||||
IO.puts(" - Pending Tasks: #{board["pending_count"]}")
|
||||
|
||||
if length(board["agents"]) > 0 do
|
||||
agent = List.first(board["agents"])
|
||||
IO.puts(" - Agent '#{agent["name"]}' is #{agent["status"]}")
|
||||
end
|
||||
|
||||
{:error, _} ->
|
||||
IO.puts("📊 Task board response: #{board_json}")
|
||||
end
|
||||
|
||||
_ ->
|
||||
IO.puts("📊 Task board response: #{inspect(board_response)}")
|
||||
end
|
||||
end
|
||||
|
||||
defp demonstrate_coordination_features do
|
||||
IO.puts("\n🤝 Testing Coordination Features...")
|
||||
|
||||
# Create a manual task for coordination
|
||||
create_task_request = %{
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => 5,
|
||||
"method" => "tools/call",
|
||||
"params" => %{
|
||||
"name" => "create_task",
|
||||
"arguments" => %{
|
||||
"title" => "Review Database Design",
|
||||
"description" => "Review the database schema for the new feature",
|
||||
"priority" => "high"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response = AgentCoordinator.UnifiedMCPServer.handle_mcp_request(create_task_request)
|
||||
IO.puts("✅ Manual task created for coordination: #{inspect(response["result"])}")
|
||||
|
||||
# Send a heartbeat
|
||||
heartbeat_request = %{
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => 6,
|
||||
"method" => "tools/call",
|
||||
"params" => %{
|
||||
"name" => "heartbeat",
|
||||
"arguments" => %{
|
||||
"agent_id" => "github_copilot_session"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
heartbeat_response = AgentCoordinator.UnifiedMCPServer.handle_mcp_request(heartbeat_request)
|
||||
IO.puts("✅ Heartbeat sent: #{inspect(heartbeat_response["result"])}")
|
||||
|
||||
IO.puts("\n💡 Coordination tools are seamlessly integrated:")
|
||||
IO.puts(" • Agents can still create tasks manually for planning")
|
||||
IO.puts(" • Heartbeats maintain agent liveness")
|
||||
IO.puts(" • Task board shows both auto and manual tasks")
|
||||
IO.puts(" • All operations work through the single unified interface")
|
||||
end
|
||||
end
|
||||
|
||||
# Run the demo
|
||||
UnifiedDemo.run()
|
||||
Reference in New Issue
Block a user