- Add FUTURE_DEVELOPMENT.md with comprehensive v2 protocol specification - Add MCP integration design and implementation foundation - Add infrastructure and deployment configurations - Update system architecture for v2 evolution 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
628 lines
16 KiB
Go
628 lines
16 KiB
Go
package mcp
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/anthonyrawlins/bzzz/logging"
|
|
"github.com/anthonyrawlins/bzzz/p2p"
|
|
"github.com/anthonyrawlins/bzzz/pubsub"
|
|
"github.com/gorilla/websocket"
|
|
"github.com/sashabaranov/go-openai"
|
|
)
|
|
|
|
// McpServer integrates BZZZ P2P network with MCP protocol for GPT-4 agents
|
|
type McpServer struct {
|
|
// Core components
|
|
p2pNode *p2p.Node
|
|
pubsub *pubsub.PubSub
|
|
hlog *logging.HypercoreLog
|
|
openaiClient *openai.Client
|
|
|
|
// Agent management
|
|
agents map[string]*GPTAgent
|
|
agentsMutex sync.RWMutex
|
|
|
|
// Server configuration
|
|
httpServer *http.Server
|
|
wsUpgrader websocket.Upgrader
|
|
|
|
// Context and lifecycle
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
|
|
// Statistics and monitoring
|
|
stats *ServerStats
|
|
}
|
|
|
|
// ServerStats tracks MCP server performance metrics
|
|
type ServerStats struct {
|
|
StartTime time.Time
|
|
TotalRequests int64
|
|
ActiveAgents int
|
|
MessagesProcessed int64
|
|
TokensConsumed int64
|
|
AverageCostPerTask float64
|
|
ErrorRate float64
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// GPTAgent represents a GPT-4 agent integrated with BZZZ network
|
|
type GPTAgent struct {
|
|
ID string
|
|
Role AgentRole
|
|
Model string
|
|
SystemPrompt string
|
|
Capabilities []string
|
|
Specialization string
|
|
MaxTasks int
|
|
|
|
// State management
|
|
Status AgentStatus
|
|
CurrentTasks map[string]*AgentTask
|
|
Memory *AgentMemory
|
|
|
|
// Cost tracking
|
|
TokenUsage *TokenUsage
|
|
CostLimits *CostLimits
|
|
|
|
// P2P Integration
|
|
NodeID string
|
|
LastAnnouncement time.Time
|
|
|
|
// Conversation participation
|
|
ActiveThreads map[string]*ConversationThread
|
|
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// AgentRole defines the role and responsibilities of an agent
|
|
type AgentRole string
|
|
|
|
const (
|
|
RoleArchitect AgentRole = "architect"
|
|
RoleReviewer AgentRole = "reviewer"
|
|
RoleDocumentation AgentRole = "documentation"
|
|
RoleDeveloper AgentRole = "developer"
|
|
RoleTester AgentRole = "tester"
|
|
RoleSecurityExpert AgentRole = "security_expert"
|
|
RoleDevOps AgentRole = "devops"
|
|
)
|
|
|
|
// AgentStatus represents the current state of an agent
|
|
type AgentStatus string
|
|
|
|
const (
|
|
StatusIdle AgentStatus = "idle"
|
|
StatusActive AgentStatus = "active"
|
|
StatusCollaborating AgentStatus = "collaborating"
|
|
StatusEscalating AgentStatus = "escalating"
|
|
StatusTerminating AgentStatus = "terminating"
|
|
)
|
|
|
|
// AgentTask represents a task being worked on by an agent
|
|
type AgentTask struct {
|
|
ID string
|
|
Title string
|
|
Repository string
|
|
Number int
|
|
StartTime time.Time
|
|
Status string
|
|
ThreadID string
|
|
Context map[string]interface{}
|
|
}
|
|
|
|
// AgentMemory manages agent memory and learning
|
|
type AgentMemory struct {
|
|
WorkingMemory map[string]interface{}
|
|
EpisodicMemory []ConversationEpisode
|
|
SemanticMemory *KnowledgeGraph
|
|
ThreadMemories map[string]*ThreadMemory
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// ConversationEpisode represents a past interaction
|
|
type ConversationEpisode struct {
|
|
Timestamp time.Time
|
|
Participants []string
|
|
Topic string
|
|
Summary string
|
|
Outcome string
|
|
Lessons []string
|
|
TokensUsed int
|
|
}
|
|
|
|
// ConversationThread represents an active conversation
|
|
type ConversationThread struct {
|
|
ID string
|
|
Topic string
|
|
Participants []AgentParticipant
|
|
Messages []ThreadMessage
|
|
State ThreadState
|
|
SharedContext map[string]interface{}
|
|
DecisionLog []Decision
|
|
CreatedAt time.Time
|
|
LastActivity time.Time
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// AgentParticipant represents an agent participating in a conversation
|
|
type AgentParticipant struct {
|
|
AgentID string
|
|
Role AgentRole
|
|
Status ParticipantStatus
|
|
}
|
|
|
|
// ParticipantStatus represents the status of a participant in a conversation
|
|
type ParticipantStatus string
|
|
|
|
const (
|
|
ParticipantStatusInvited ParticipantStatus = "invited"
|
|
ParticipantStatusActive ParticipantStatus = "active"
|
|
ParticipantStatusIdle ParticipantStatus = "idle"
|
|
ParticipantStatusLeft ParticipantStatus = "left"
|
|
)
|
|
|
|
// ThreadMessage represents a message in a conversation thread
|
|
type ThreadMessage struct {
|
|
ID string
|
|
From string
|
|
Role AgentRole
|
|
Content string
|
|
MessageType pubsub.MessageType
|
|
Timestamp time.Time
|
|
ReplyTo string
|
|
TokenCount int
|
|
Model string
|
|
}
|
|
|
|
// ThreadState represents the state of a conversation thread
|
|
type ThreadState string
|
|
|
|
const (
|
|
ThreadStateActive ThreadState = "active"
|
|
ThreadStateCompleted ThreadState = "completed"
|
|
ThreadStateEscalated ThreadState = "escalated"
|
|
ThreadStateClosed ThreadState = "closed"
|
|
)
|
|
|
|
// Decision represents a decision made in a conversation
|
|
type Decision struct {
|
|
ID string
|
|
Description string
|
|
DecidedBy []string
|
|
Timestamp time.Time
|
|
Rationale string
|
|
Confidence float64
|
|
}
|
|
|
|
// NewMcpServer creates a new MCP server instance
|
|
func NewMcpServer(
|
|
ctx context.Context,
|
|
node *p2p.Node,
|
|
ps *pubsub.PubSub,
|
|
hlog *logging.HypercoreLog,
|
|
openaiAPIKey string,
|
|
) *McpServer {
|
|
serverCtx, cancel := context.WithCancel(ctx)
|
|
|
|
server := &McpServer{
|
|
p2pNode: node,
|
|
pubsub: ps,
|
|
hlog: hlog,
|
|
openaiClient: openai.NewClient(openaiAPIKey),
|
|
agents: make(map[string]*GPTAgent),
|
|
ctx: serverCtx,
|
|
cancel: cancel,
|
|
wsUpgrader: websocket.Upgrader{
|
|
CheckOrigin: func(r *http.Request) bool { return true },
|
|
},
|
|
stats: &ServerStats{
|
|
StartTime: time.Now(),
|
|
},
|
|
}
|
|
|
|
return server
|
|
}
|
|
|
|
// Start initializes and starts the MCP server
|
|
func (s *McpServer) Start(port int) error {
|
|
// Set up HTTP handlers
|
|
mux := http.NewServeMux()
|
|
|
|
// MCP WebSocket endpoint
|
|
mux.HandleFunc("/mcp", s.handleMCPWebSocket)
|
|
|
|
// REST API endpoints
|
|
mux.HandleFunc("/api/agents", s.handleAgentsAPI)
|
|
mux.HandleFunc("/api/conversations", s.handleConversationsAPI)
|
|
mux.HandleFunc("/api/stats", s.handleStatsAPI)
|
|
mux.HandleFunc("/health", s.handleHealthCheck)
|
|
|
|
// Start HTTP server
|
|
s.httpServer = &http.Server{
|
|
Addr: fmt.Sprintf(":%d", port),
|
|
Handler: mux,
|
|
}
|
|
|
|
go func() {
|
|
if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
fmt.Printf("❌ MCP HTTP server error: %v\n", err)
|
|
}
|
|
}()
|
|
|
|
// Start message handlers
|
|
go s.handleBzzzMessages()
|
|
go s.handleHmmmMessages()
|
|
|
|
// Start periodic tasks
|
|
go s.periodicTasks()
|
|
|
|
fmt.Printf("🚀 MCP Server started on port %d\n", port)
|
|
return nil
|
|
}
|
|
|
|
// Stop gracefully shuts down the MCP server
|
|
func (s *McpServer) Stop() error {
|
|
s.cancel()
|
|
|
|
// Stop all agents
|
|
s.agentsMutex.Lock()
|
|
for _, agent := range s.agents {
|
|
s.stopAgent(agent)
|
|
}
|
|
s.agentsMutex.Unlock()
|
|
|
|
// Stop HTTP server
|
|
if s.httpServer != nil {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
return s.httpServer.Shutdown(ctx)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CreateGPTAgent creates a new GPT-4 agent
|
|
func (s *McpServer) CreateGPTAgent(config *AgentConfig) (*GPTAgent, error) {
|
|
agent := &GPTAgent{
|
|
ID: config.ID,
|
|
Role: config.Role,
|
|
Model: config.Model,
|
|
SystemPrompt: config.SystemPrompt,
|
|
Capabilities: config.Capabilities,
|
|
Specialization: config.Specialization,
|
|
MaxTasks: config.MaxTasks,
|
|
Status: StatusIdle,
|
|
CurrentTasks: make(map[string]*AgentTask),
|
|
Memory: NewAgentMemory(),
|
|
TokenUsage: NewTokenUsage(),
|
|
CostLimits: config.CostLimits,
|
|
NodeID: s.p2pNode.ID().ShortString(),
|
|
ActiveThreads: make(map[string]*ConversationThread),
|
|
}
|
|
|
|
s.agentsMutex.Lock()
|
|
s.agents[agent.ID] = agent
|
|
s.agentsMutex.Unlock()
|
|
|
|
// Announce agent to BZZZ network
|
|
if err := s.announceAgent(agent); err != nil {
|
|
return nil, fmt.Errorf("failed to announce agent: %w", err)
|
|
}
|
|
|
|
s.hlog.Append(logging.PeerJoined, map[string]interface{}{
|
|
"agent_id": agent.ID,
|
|
"role": string(agent.Role),
|
|
"capabilities": agent.Capabilities,
|
|
"specialization": agent.Specialization,
|
|
})
|
|
|
|
fmt.Printf("✅ Created GPT-4 agent: %s (%s)\n", agent.ID, agent.Role)
|
|
return agent, nil
|
|
}
|
|
|
|
// ProcessCollaborativeTask handles a task that requires multi-agent collaboration
|
|
func (s *McpServer) ProcessCollaborativeTask(
|
|
task *AgentTask,
|
|
requiredRoles []AgentRole,
|
|
) (*ConversationThread, error) {
|
|
|
|
// Create conversation thread
|
|
thread := &ConversationThread{
|
|
ID: fmt.Sprintf("task-%s-%d", task.Repository, task.Number),
|
|
Topic: fmt.Sprintf("Collaborative Task: %s", task.Title),
|
|
State: ThreadStateActive,
|
|
SharedContext: map[string]interface{}{
|
|
"task": task,
|
|
"required_roles": requiredRoles,
|
|
},
|
|
CreatedAt: time.Now(),
|
|
LastActivity: time.Now(),
|
|
}
|
|
|
|
// Find and invite agents
|
|
for _, role := range requiredRoles {
|
|
agents := s.findAgentsByRole(role)
|
|
if len(agents) == 0 {
|
|
return nil, fmt.Errorf("no available agents for role: %s", role)
|
|
}
|
|
|
|
// Select best agent for this role
|
|
selectedAgent := s.selectBestAgent(agents, task)
|
|
|
|
thread.Participants = append(thread.Participants, AgentParticipant{
|
|
AgentID: selectedAgent.ID,
|
|
Role: role,
|
|
Status: ParticipantStatusInvited,
|
|
})
|
|
|
|
// Add thread to agent
|
|
selectedAgent.mutex.Lock()
|
|
selectedAgent.ActiveThreads[thread.ID] = thread
|
|
selectedAgent.mutex.Unlock()
|
|
}
|
|
|
|
// Send initial collaboration request
|
|
if err := s.initiateCollaboration(thread); err != nil {
|
|
return nil, fmt.Errorf("failed to initiate collaboration: %w", err)
|
|
}
|
|
|
|
return thread, nil
|
|
}
|
|
|
|
// handleMCPWebSocket handles WebSocket connections for MCP protocol
|
|
func (s *McpServer) handleMCPWebSocket(w http.ResponseWriter, r *http.Request) {
|
|
conn, err := s.wsUpgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
fmt.Printf("❌ WebSocket upgrade failed: %v\n", err)
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
fmt.Printf("📡 MCP WebSocket connection established\n")
|
|
|
|
// Handle MCP protocol messages
|
|
for {
|
|
var message map[string]interface{}
|
|
if err := conn.ReadJSON(&message); err != nil {
|
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
|
fmt.Printf("❌ WebSocket error: %v\n", err)
|
|
}
|
|
break
|
|
}
|
|
|
|
// Process MCP message
|
|
response, err := s.processMCPMessage(message)
|
|
if err != nil {
|
|
fmt.Printf("❌ MCP message processing error: %v\n", err)
|
|
response = map[string]interface{}{
|
|
"error": err.Error(),
|
|
}
|
|
}
|
|
|
|
if err := conn.WriteJSON(response); err != nil {
|
|
fmt.Printf("❌ WebSocket write error: %v\n", err)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// processMCPMessage processes incoming MCP protocol messages
|
|
func (s *McpServer) processMCPMessage(message map[string]interface{}) (map[string]interface{}, error) {
|
|
method, ok := message["method"].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("missing or invalid method")
|
|
}
|
|
|
|
params, _ := message["params"].(map[string]interface{})
|
|
|
|
switch method {
|
|
case "tools/list":
|
|
return s.listTools(), nil
|
|
case "tools/call":
|
|
return s.callTool(params)
|
|
case "resources/list":
|
|
return s.listResources(), nil
|
|
case "resources/read":
|
|
return s.readResource(params)
|
|
default:
|
|
return nil, fmt.Errorf("unknown method: %s", method)
|
|
}
|
|
}
|
|
|
|
// callTool handles tool execution requests
|
|
func (s *McpServer) callTool(params map[string]interface{}) (map[string]interface{}, error) {
|
|
toolName, ok := params["name"].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("missing tool name")
|
|
}
|
|
|
|
args, _ := params["arguments"].(map[string]interface{})
|
|
|
|
switch toolName {
|
|
case "bzzz_announce":
|
|
return s.handleBzzzAnnounce(args)
|
|
case "bzzz_lookup":
|
|
return s.handleBzzzLookup(args)
|
|
case "bzzz_get":
|
|
return s.handleBzzzGet(args)
|
|
case "bzzz_post":
|
|
return s.handleBzzzPost(args)
|
|
case "bzzz_thread":
|
|
return s.handleBzzzThread(args)
|
|
case "bzzz_subscribe":
|
|
return s.handleBzzzSubscribe(args)
|
|
default:
|
|
return nil, fmt.Errorf("unknown tool: %s", toolName)
|
|
}
|
|
}
|
|
|
|
// handleBzzzAnnounce implements the bzzz_announce tool
|
|
func (s *McpServer) handleBzzzAnnounce(args map[string]interface{}) (map[string]interface{}, error) {
|
|
agentID, ok := args["agent_id"].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("agent_id is required")
|
|
}
|
|
|
|
role, ok := args["role"].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("role is required")
|
|
}
|
|
|
|
// Create announcement message
|
|
announcement := map[string]interface{}{
|
|
"agent_id": agentID,
|
|
"role": role,
|
|
"capabilities": args["capabilities"],
|
|
"specialization": args["specialization"],
|
|
"max_tasks": args["max_tasks"],
|
|
"announced_at": time.Now(),
|
|
"node_id": s.p2pNode.ID().ShortString(),
|
|
}
|
|
|
|
// Publish to BZZZ network
|
|
if err := s.pubsub.PublishBzzzMessage(pubsub.CapabilityBcast, announcement); err != nil {
|
|
return nil, fmt.Errorf("failed to announce: %w", err)
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"success": true,
|
|
"message": fmt.Sprintf("Agent %s (%s) announced to network", agentID, role),
|
|
}, nil
|
|
}
|
|
|
|
// Additional tool handlers would be implemented here...
|
|
|
|
// Helper methods
|
|
|
|
// announceAgent announces an agent to the BZZZ network
|
|
func (s *McpServer) announceAgent(agent *GPTAgent) error {
|
|
announcement := map[string]interface{}{
|
|
"type": "gpt_agent_announcement",
|
|
"agent_id": agent.ID,
|
|
"role": string(agent.Role),
|
|
"capabilities": agent.Capabilities,
|
|
"specialization": agent.Specialization,
|
|
"max_tasks": agent.MaxTasks,
|
|
"model": agent.Model,
|
|
"node_id": agent.NodeID,
|
|
"timestamp": time.Now(),
|
|
}
|
|
|
|
return s.pubsub.PublishBzzzMessage(pubsub.CapabilityBcast, announcement)
|
|
}
|
|
|
|
// findAgentsByRole finds all agents with a specific role
|
|
func (s *McpServer) findAgentsByRole(role AgentRole) []*GPTAgent {
|
|
s.agentsMutex.RLock()
|
|
defer s.agentsMutex.RUnlock()
|
|
|
|
var agents []*GPTAgent
|
|
for _, agent := range s.agents {
|
|
if agent.Role == role && agent.Status == StatusIdle {
|
|
agents = append(agents, agent)
|
|
}
|
|
}
|
|
|
|
return agents
|
|
}
|
|
|
|
// selectBestAgent selects the best agent for a task
|
|
func (s *McpServer) selectBestAgent(agents []*GPTAgent, task *AgentTask) *GPTAgent {
|
|
if len(agents) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Simple selection: least busy agent
|
|
bestAgent := agents[0]
|
|
for _, agent := range agents[1:] {
|
|
if len(agent.CurrentTasks) < len(bestAgent.CurrentTasks) {
|
|
bestAgent = agent
|
|
}
|
|
}
|
|
|
|
return bestAgent
|
|
}
|
|
|
|
// Additional helper methods would be implemented here...
|
|
|
|
// AgentConfig holds configuration for creating a new agent
|
|
type AgentConfig struct {
|
|
ID string
|
|
Role AgentRole
|
|
Model string
|
|
SystemPrompt string
|
|
Capabilities []string
|
|
Specialization string
|
|
MaxTasks int
|
|
CostLimits *CostLimits
|
|
}
|
|
|
|
// CostLimits defines spending limits for an agent
|
|
type CostLimits struct {
|
|
DailyLimit float64
|
|
MonthlyLimit float64
|
|
PerTaskLimit float64
|
|
}
|
|
|
|
// TokenUsage tracks token consumption
|
|
type TokenUsage struct {
|
|
TotalTokens int64
|
|
PromptTokens int64
|
|
CompletionTokens int64
|
|
TotalCost float64
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// NewTokenUsage creates a new token usage tracker
|
|
func NewTokenUsage() *TokenUsage {
|
|
return &TokenUsage{}
|
|
}
|
|
|
|
// NewAgentMemory creates a new agent memory instance
|
|
func NewAgentMemory() *AgentMemory {
|
|
return &AgentMemory{
|
|
WorkingMemory: make(map[string]interface{}),
|
|
EpisodicMemory: make([]ConversationEpisode, 0),
|
|
ThreadMemories: make(map[string]*ThreadMemory),
|
|
}
|
|
}
|
|
|
|
// ThreadMemory represents memory for a specific conversation thread
|
|
type ThreadMemory struct {
|
|
ThreadID string
|
|
Summary string
|
|
KeyPoints []string
|
|
Decisions []Decision
|
|
LastUpdated time.Time
|
|
}
|
|
|
|
// KnowledgeGraph represents semantic knowledge
|
|
type KnowledgeGraph struct {
|
|
Concepts map[string]*Concept
|
|
Relations map[string]*Relation
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// Concept represents a knowledge concept
|
|
type Concept struct {
|
|
ID string
|
|
Name string
|
|
Description string
|
|
Category string
|
|
Confidence float64
|
|
}
|
|
|
|
// Relation represents a relationship between concepts
|
|
type Relation struct {
|
|
From string
|
|
To string
|
|
Type string
|
|
Strength float64
|
|
Evidence []string
|
|
} |