Remove duplicate MCP server files - consolidation complete

- Deleted mcp_server_manager.ex, enhanced_mcp_server.ex, unified_mcp_server.ex
- All functionality successfully consolidated into single mcp_server.ex
- Server starts correctly with all external servers (context7, filesystem, memory, sequentialthinking)
- HTTP server support working for mcp_figma
- All 15+ agent coordination tools properly registered
- Codebase is now clean with no duplicate files
This commit is contained in:
Ra
2025-09-03 00:24:06 -07:00
parent ea3c390257
commit 7cb42e4131
3 changed files with 0 additions and 1624 deletions

View File

@@ -1,266 +0,0 @@
defmodule AgentCoordinator.EnhancedMCPServer do
@moduledoc """
Enhanced MCP server with automatic heartbeat management and collision detection.
This module extends the base MCP server with:
1. Automatic heartbeats on every operation
2. Agent session tracking
3. Enhanced collision detection
4. Automatic agent cleanup on disconnect
"""
use GenServer
alias AgentCoordinator.{MCPServer, AutoHeartbeat, TaskRegistry}
# Track active agent sessions
defstruct [
:agent_sessions,
:session_monitors
]
# Client API
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@doc """
Enhanced MCP request handler with automatic heartbeat management
"""
def handle_enhanced_mcp_request(request, session_info \\ %{}) do
GenServer.call(__MODULE__, {:enhanced_mcp_request, request, session_info})
end
@doc """
Register an agent with enhanced session tracking
"""
def register_agent_with_session(name, capabilities, session_pid \\ self()) do
GenServer.call(__MODULE__, {:register_agent_with_session, name, capabilities, session_pid})
end
# Server callbacks
def init(_opts) do
state = %__MODULE__{
agent_sessions: %{},
session_monitors: %{}
}
{:ok, state}
end
def handle_call({:enhanced_mcp_request, request, session_info}, {from_pid, _}, state) do
# Extract agent_id from session or request
agent_id = extract_agent_id(request, session_info, state)
# If we have an agent_id, send heartbeat before and after operation
enhanced_result =
case agent_id do
nil ->
# No agent context, use normal MCP processing
MCPServer.handle_mcp_request(request)
id ->
# Send pre-operation heartbeat
pre_heartbeat = TaskRegistry.heartbeat_agent(id)
# Process the request
result = MCPServer.handle_mcp_request(request)
# Send post-operation heartbeat and update session activity
post_heartbeat = TaskRegistry.heartbeat_agent(id)
update_session_activity(state, id, from_pid)
# Add heartbeat metadata to successful responses
case result do
%{"result" => _} = success ->
Map.put(success, "_heartbeat_metadata", %{
agent_id: id,
pre_heartbeat: pre_heartbeat,
post_heartbeat: post_heartbeat,
timestamp: DateTime.utc_now()
})
error_result ->
error_result
end
end
{:reply, enhanced_result, state}
end
def handle_call({:register_agent_with_session, name, capabilities, session_pid}, _from, state) do
# Convert capabilities to strings if they're atoms
string_capabilities =
Enum.map(capabilities, fn
cap when is_atom(cap) -> Atom.to_string(cap)
cap when is_binary(cap) -> cap
end)
# Register the agent normally first
case MCPServer.handle_mcp_request(%{
"method" => "tools/call",
"params" => %{
"name" => "register_agent",
"arguments" => %{"name" => name, "capabilities" => string_capabilities}
}
}) do
%{"result" => %{"content" => [%{"text" => response_json}]}} ->
case Jason.decode(response_json) do
{:ok, %{"agent_id" => agent_id}} ->
# Track the session
monitor_ref = Process.monitor(session_pid)
new_state = %{
state
| agent_sessions:
Map.put(state.agent_sessions, agent_id, %{
pid: session_pid,
name: name,
capabilities: capabilities,
registered_at: DateTime.utc_now(),
last_activity: DateTime.utc_now()
}),
session_monitors: Map.put(state.session_monitors, monitor_ref, agent_id)
}
# Start automatic heartbeat management
AutoHeartbeat.start_link([])
AutoHeartbeat.register_agent_with_heartbeat(name, capabilities, %{
session_pid: session_pid,
enhanced_server: true
})
{:reply, {:ok, agent_id}, new_state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
%{"error" => %{"message" => message}} ->
{:reply, {:error, message}, state}
_ ->
{:reply, {:error, "Unexpected response format"}, state}
end
end
def handle_call(:get_enhanced_task_board, _from, state) do
# Get the regular task board
case MCPServer.handle_mcp_request(%{
"method" => "tools/call",
"params" => %{"name" => "get_task_board", "arguments" => %{}}
}) do
%{"result" => %{"content" => [%{"text" => response_json}]}} ->
case Jason.decode(response_json) do
{:ok, %{"agents" => agents}} ->
# Enhance with session information
enhanced_agents =
Enum.map(agents, fn agent ->
agent_id = agent["agent_id"]
session_info = Map.get(state.agent_sessions, agent_id, %{})
Map.merge(agent, %{
"session_active" => Map.has_key?(state.agent_sessions, agent_id),
"last_activity" => Map.get(session_info, :last_activity),
"session_duration" => calculate_session_duration(session_info)
})
end)
result = %{
"agents" => enhanced_agents,
"active_sessions" => map_size(state.agent_sessions)
}
{:reply, {:ok, result}, state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
%{"error" => %{"message" => message}} ->
{:reply, {:error, message}, state}
end
end
# Handle process monitoring - cleanup when agent session dies
def handle_info({:DOWN, monitor_ref, :process, _pid, _reason}, state) do
case Map.get(state.session_monitors, monitor_ref) do
nil ->
{:noreply, state}
agent_id ->
# Clean up the agent session
new_state = %{
state
| agent_sessions: Map.delete(state.agent_sessions, agent_id),
session_monitors: Map.delete(state.session_monitors, monitor_ref)
}
# Stop heartbeat management
AutoHeartbeat.stop_heartbeat(agent_id)
# Mark agent as offline in registry
# (This could be enhanced to gracefully handle ongoing tasks)
{:noreply, new_state}
end
end
# Private helpers
defp extract_agent_id(request, session_info, state) do
# Try to get agent_id from various sources
cond do
# From request arguments
Map.get(request, "params", %{})
|> Map.get("arguments", %{})
|> Map.get("agent_id") ->
request["params"]["arguments"]["agent_id"]
# From session info
Map.get(session_info, :agent_id) ->
session_info.agent_id
# From session lookup by PID
session_pid = Map.get(session_info, :session_pid, self()) ->
find_agent_by_session_pid(state, session_pid)
true ->
nil
end
end
defp find_agent_by_session_pid(state, session_pid) do
Enum.find_value(state.agent_sessions, fn {agent_id, session_data} ->
if session_data.pid == session_pid, do: agent_id, else: nil
end)
end
defp update_session_activity(state, agent_id, _session_pid) do
case Map.get(state.agent_sessions, agent_id) do
nil ->
:ok
session_data ->
_updated_session = %{session_data | last_activity: DateTime.utc_now()}
# Note: This doesn't update the state since we're in a call handler
# In a real implementation, you might want to use cast for this
:ok
end
end
@doc """
Get enhanced task board with session information
"""
def get_enhanced_task_board do
GenServer.call(__MODULE__, :get_enhanced_task_board)
end
defp calculate_session_duration(%{registered_at: start_time}) do
DateTime.diff(DateTime.utc_now(), start_time, :second)
end
defp calculate_session_duration(_), do: nil
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,251 +0,0 @@
defmodule AgentCoordinator.UnifiedMCPServer do
@moduledoc """
Unified MCP Server that aggregates all external MCP servers and Agent Coordinator tools.
This is the single MCP server that GitHub Copilot sees, which internally manages
all other MCP servers and provides automatic task tracking for any tool usage.
"""
use GenServer
require Logger
alias AgentCoordinator.{MCPServerManager, TaskRegistry}
defstruct [
:agent_sessions,
:request_id_counter
]
# Client API
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@doc """
Handle MCP request from GitHub Copilot
"""
def handle_mcp_request(request) do
GenServer.call(__MODULE__, {:handle_request, request}, 60_000)
end
# Server callbacks
def init(_opts) do
state = %__MODULE__{
agent_sessions: %{},
request_id_counter: 0
}
IO.puts(:stderr, "Unified MCP Server starting...")
{:ok, state}
end
def handle_call({:handle_request, request}, _from, state) do
response = process_mcp_request(request, state)
{:reply, response, state}
end
def handle_call({:register_agent_session, agent_id, session_info}, _from, state) do
new_state = %{state | agent_sessions: Map.put(state.agent_sessions, agent_id, session_info)}
{:reply, :ok, new_state}
end
def handle_info(_msg, state) do
{:noreply, state}
end
# Private functions
defp process_mcp_request(request, state) do
method = Map.get(request, "method")
id = Map.get(request, "id")
case method do
"initialize" ->
handle_initialize(request, id)
"tools/list" ->
handle_tools_list(request, id)
"tools/call" ->
handle_tools_call(request, id, state)
_ ->
error_response(id, -32601, "Method not found: #{method}")
end
end
defp handle_initialize(_request, id) do
%{
"jsonrpc" => "2.0",
"id" => id,
"result" => %{
"protocolVersion" => "2024-11-05",
"capabilities" => %{
"tools" => %{},
"coordination" => %{
"automatic_task_tracking" => true,
"agent_management" => true,
"multi_server_proxy" => true,
"heartbeat_coverage" => true
}
},
"serverInfo" => %{
"name" => "agent-coordinator-unified",
"version" => "0.1.0",
"description" =>
"Unified MCP server with automatic task tracking and agent coordination"
}
}
}
end
defp handle_tools_list(_request, id) do
case MCPServerManager.get_unified_tools() do
tools when is_list(tools) ->
%{
"jsonrpc" => "2.0",
"id" => id,
"result" => %{
"tools" => tools
}
}
{:error, reason} ->
error_response(id, -32603, "Failed to get tools: #{reason}")
end
end
defp handle_tools_call(request, id, state) do
params = Map.get(request, "params", %{})
tool_name = Map.get(params, "name")
arguments = Map.get(params, "arguments", %{})
# Determine agent context from the request or session
agent_context = determine_agent_context(request, arguments, state)
case MCPServerManager.route_tool_call(tool_name, arguments, agent_context) do
%{"error" => _} = error_result ->
Map.put(error_result, "id", id)
result ->
# Wrap successful results in MCP format
success_response = %{
"jsonrpc" => "2.0",
"id" => id,
"result" => format_tool_result(result, tool_name, agent_context)
}
success_response
end
end
defp determine_agent_context(request, arguments, state) do
# Try to determine agent from various sources:
# 1. Explicit agent_id in arguments
case Map.get(arguments, "agent_id") do
agent_id when is_binary(agent_id) ->
%{agent_id: agent_id}
_ ->
# 2. Try to extract from request metadata
case extract_agent_from_request(request) do
agent_id when is_binary(agent_id) ->
%{agent_id: agent_id}
_ ->
# 3. Use a default session for GitHub Copilot
default_agent_context(state)
end
end
end
defp extract_agent_from_request(_request) do
# Look for agent info in request headers, params, etc.
# This could be extended to support various ways of identifying the agent
nil
end
defp default_agent_context(state) do
# Create or use a default agent session for GitHub Copilot
default_agent_id = "github_copilot_session"
case Map.get(state.agent_sessions, default_agent_id) do
nil ->
# Auto-register GitHub Copilot as an agent
case TaskRegistry.register_agent("GitHub Copilot", [
"coding",
"analysis",
"review",
"documentation"
]) do
{:ok, %{agent_id: agent_id}} ->
session_info = %{
agent_id: agent_id,
name: "GitHub Copilot",
auto_registered: true,
created_at: DateTime.utc_now()
}
GenServer.call(self(), {:register_agent_session, agent_id, session_info})
%{agent_id: agent_id}
_ ->
%{agent_id: default_agent_id}
end
session_info ->
%{agent_id: session_info.agent_id}
end
end
defp format_tool_result(result, tool_name, agent_context) do
# Format the result according to MCP tool call response format
base_result =
case result do
%{"result" => content} when is_map(content) ->
# Already properly formatted
content
{:ok, content} ->
# Convert tuple response to content
%{"content" => [%{"type" => "text", "text" => inspect(content)}]}
%{} = map_result ->
# Convert map to text content
%{"content" => [%{"type" => "text", "text" => Jason.encode!(map_result)}]}
binary when is_binary(binary) ->
# Simple text result
%{"content" => [%{"type" => "text", "text" => binary}]}
other ->
# Fallback for any other type
%{"content" => [%{"type" => "text", "text" => inspect(other)}]}
end
# Add metadata about the operation
metadata = %{
"tool_name" => tool_name,
"agent_id" => agent_context.agent_id,
"timestamp" => DateTime.utc_now() |> DateTime.to_iso8601(),
"auto_tracked" => true
}
Map.put(base_result, "_metadata", metadata)
end
defp error_response(id, code, message) do
%{
"jsonrpc" => "2.0",
"id" => id,
"error" => %{
"code" => code,
"message" => message
}
}
end
end