Files
bzzz/pkg/mcp/server.go
anthonyrawlins 065dddf8d5 Prepare for v2 development: Add MCP integration and future development planning
- 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>
2025-08-07 14:38:22 +10:00

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
}