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>
This commit is contained in:
493
mcp-server/src/tools/protocol-tools.ts
Normal file
493
mcp-server/src/tools/protocol-tools.ts
Normal file
@@ -0,0 +1,493 @@
|
||||
import { Logger } from '../utils/logger.js';
|
||||
import { AgentManager } from '../agents/agent-manager.js';
|
||||
import { ConversationManager } from '../conversations/conversation-manager.js';
|
||||
import { BzzzP2PConnector } from '../p2p/bzzz-connector.js';
|
||||
|
||||
export interface SemanticAddress {
|
||||
agent?: string;
|
||||
role?: string;
|
||||
project?: string;
|
||||
task?: string;
|
||||
path?: string;
|
||||
raw: string;
|
||||
}
|
||||
|
||||
export interface ProtocolToolsConfig {
|
||||
agentManager: AgentManager;
|
||||
p2pConnector: BzzzP2PConnector;
|
||||
conversationManager: ConversationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* BzzzProtocolTools implements the core BZZZ protocol operations as MCP tools
|
||||
*/
|
||||
export class BzzzProtocolTools {
|
||||
private logger: Logger;
|
||||
private agentManager: AgentManager;
|
||||
private p2pConnector: BzzzP2PConnector;
|
||||
private conversationManager: ConversationManager;
|
||||
|
||||
constructor(config: ProtocolToolsConfig) {
|
||||
this.logger = new Logger('BzzzProtocolTools');
|
||||
this.agentManager = config.agentManager;
|
||||
this.p2pConnector = config.p2pConnector;
|
||||
this.conversationManager = config.conversationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle bzzz_announce - Agent presence announcement
|
||||
*/
|
||||
async handleAnnounce(args: Record<string, any>): Promise<any> {
|
||||
const { agent_id, role, capabilities, specialization, max_tasks = 3 } = args;
|
||||
|
||||
if (!agent_id || !role) {
|
||||
throw new Error('agent_id and role are required for announcement');
|
||||
}
|
||||
|
||||
this.logger.info(`Announcing agent ${agent_id} with role ${role}`);
|
||||
|
||||
try {
|
||||
// Create or update agent
|
||||
const agent = await this.agentManager.createAgent({
|
||||
id: agent_id,
|
||||
role,
|
||||
capabilities: capabilities || [],
|
||||
specialization: specialization || role,
|
||||
maxTasks: max_tasks,
|
||||
});
|
||||
|
||||
// Announce to P2P network
|
||||
const announcement = {
|
||||
type: 'capability_broadcast',
|
||||
agent_id,
|
||||
role,
|
||||
capabilities: capabilities || [],
|
||||
specialization: specialization || role,
|
||||
max_tasks,
|
||||
timestamp: new Date().toISOString(),
|
||||
network_address: this.p2pConnector.getNodeId(),
|
||||
};
|
||||
|
||||
await this.p2pConnector.publishMessage('bzzz/coordination/v1', announcement);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Agent ${agent_id} (${role}) announced to BZZZ network`,
|
||||
agent: {
|
||||
id: agent.id,
|
||||
role: agent.role,
|
||||
capabilities: agent.capabilities,
|
||||
specialization: agent.specialization,
|
||||
status: agent.status,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to announce agent:', error);
|
||||
throw new Error(`Announcement failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle bzzz_lookup - Semantic address discovery
|
||||
*/
|
||||
async handleLookup(args: Record<string, any>): Promise<any> {
|
||||
const { semantic_address, filter_criteria = {} } = args;
|
||||
|
||||
if (!semantic_address) {
|
||||
throw new Error('semantic_address is required for lookup');
|
||||
}
|
||||
|
||||
this.logger.info(`Looking up semantic address: ${semantic_address}`);
|
||||
|
||||
try {
|
||||
// Parse semantic address
|
||||
const address = this.parseSemanticAddress(semantic_address);
|
||||
|
||||
// Discover matching agents
|
||||
const agents = await this.discoverAgents(address, filter_criteria);
|
||||
|
||||
// Query P2P network for additional matches
|
||||
const networkResults = await this.queryP2PNetwork(address);
|
||||
|
||||
// Combine and rank results
|
||||
const allMatches = [...agents, ...networkResults];
|
||||
const rankedMatches = this.rankMatches(allMatches, address, filter_criteria);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
address: semantic_address,
|
||||
parsed_address: address,
|
||||
matches: rankedMatches,
|
||||
count: rankedMatches.length,
|
||||
query_time: new Date().toISOString(),
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to lookup address:', error);
|
||||
throw new Error(`Lookup failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle bzzz_get - Content retrieval from addresses
|
||||
*/
|
||||
async handleGet(args: Record<string, any>): Promise<any> {
|
||||
const { address, include_metadata = true, max_history = 10 } = args;
|
||||
|
||||
if (!address) {
|
||||
throw new Error('address is required for get operation');
|
||||
}
|
||||
|
||||
this.logger.info(`Getting content from address: ${address}`);
|
||||
|
||||
try {
|
||||
const parsedAddress = this.parseSemanticAddress(address);
|
||||
|
||||
// Retrieve content based on address type
|
||||
let content;
|
||||
let metadata = {};
|
||||
|
||||
if (parsedAddress.agent) {
|
||||
// Get agent-specific content
|
||||
content = await this.getAgentContent(parsedAddress, max_history);
|
||||
if (include_metadata) {
|
||||
metadata = await this.getAgentMetadata(parsedAddress.agent);
|
||||
}
|
||||
} else if (parsedAddress.project) {
|
||||
// Get project-specific content
|
||||
content = await this.getProjectContent(parsedAddress, max_history);
|
||||
if (include_metadata) {
|
||||
metadata = await this.getProjectMetadata(parsedAddress.project);
|
||||
}
|
||||
} else {
|
||||
// General network query
|
||||
content = await this.getNetworkContent(parsedAddress, max_history);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
address,
|
||||
content,
|
||||
metadata: include_metadata ? metadata : undefined,
|
||||
retrieved_at: new Date().toISOString(),
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to get content:', error);
|
||||
throw new Error(`Get operation failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle bzzz_post - Event/message posting
|
||||
*/
|
||||
async handlePost(args: Record<string, any>): Promise<any> {
|
||||
const { target_address, message_type, content, priority = 'medium', thread_id } = args;
|
||||
|
||||
if (!target_address || !message_type || !content) {
|
||||
throw new Error('target_address, message_type, and content are required for post operation');
|
||||
}
|
||||
|
||||
this.logger.info(`Posting ${message_type} to address: ${target_address}`);
|
||||
|
||||
try {
|
||||
const parsedAddress = this.parseSemanticAddress(target_address);
|
||||
|
||||
// Create message payload
|
||||
const message = {
|
||||
type: message_type,
|
||||
content,
|
||||
priority,
|
||||
thread_id,
|
||||
target_address,
|
||||
sender_id: this.p2pConnector.getNodeId(),
|
||||
timestamp: new Date().toISOString(),
|
||||
parsed_address: parsedAddress,
|
||||
};
|
||||
|
||||
// Determine routing strategy
|
||||
let deliveryResults;
|
||||
|
||||
if (parsedAddress.agent) {
|
||||
// Direct agent messaging
|
||||
deliveryResults = await this.postToAgent(parsedAddress.agent, message);
|
||||
} else if (parsedAddress.role) {
|
||||
// Role-based broadcasting
|
||||
deliveryResults = await this.postToRole(parsedAddress.role, message);
|
||||
} else if (parsedAddress.project) {
|
||||
// Project-specific messaging
|
||||
deliveryResults = await this.postToProject(parsedAddress.project, message);
|
||||
} else {
|
||||
// General network broadcast
|
||||
deliveryResults = await this.postToNetwork(message);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message_id: this.generateMessageId(),
|
||||
target_address,
|
||||
message_type,
|
||||
delivery_results: deliveryResults,
|
||||
posted_at: new Date().toISOString(),
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to post message:', error);
|
||||
throw new Error(`Post operation failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle bzzz_thread - Conversation management
|
||||
*/
|
||||
async handleThread(args: Record<string, any>): Promise<any> {
|
||||
const { action, thread_id, participants, topic } = args;
|
||||
|
||||
if (!action) {
|
||||
throw new Error('action is required for thread operation');
|
||||
}
|
||||
|
||||
this.logger.info(`Thread action: ${action}, thread_id: ${thread_id}`);
|
||||
|
||||
try {
|
||||
let result;
|
||||
|
||||
switch (action) {
|
||||
case 'create':
|
||||
if (!topic || !participants?.length) {
|
||||
throw new Error('topic and participants are required for creating threads');
|
||||
}
|
||||
result = await this.conversationManager.createThread({
|
||||
topic,
|
||||
participants,
|
||||
creator: this.p2pConnector.getNodeId(),
|
||||
});
|
||||
break;
|
||||
|
||||
case 'join':
|
||||
if (!thread_id) {
|
||||
throw new Error('thread_id is required for joining threads');
|
||||
}
|
||||
result = await this.conversationManager.joinThread(
|
||||
thread_id,
|
||||
this.p2pConnector.getNodeId()
|
||||
);
|
||||
break;
|
||||
|
||||
case 'leave':
|
||||
if (!thread_id) {
|
||||
throw new Error('thread_id is required for leaving threads');
|
||||
}
|
||||
result = await this.conversationManager.leaveThread(
|
||||
thread_id,
|
||||
this.p2pConnector.getNodeId()
|
||||
);
|
||||
break;
|
||||
|
||||
case 'list':
|
||||
result = await this.conversationManager.listThreads(
|
||||
this.p2pConnector.getNodeId()
|
||||
);
|
||||
break;
|
||||
|
||||
case 'summarize':
|
||||
if (!thread_id) {
|
||||
throw new Error('thread_id is required for summarizing threads');
|
||||
}
|
||||
result = await this.conversationManager.summarizeThread(thread_id);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown thread action: ${action}`);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
action,
|
||||
thread_id,
|
||||
result,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Thread operation failed:', error);
|
||||
throw new Error(`Thread operation failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle bzzz_subscribe - Real-time event subscription
|
||||
*/
|
||||
async handleSubscribe(args: Record<string, any>): Promise<any> {
|
||||
const { event_types, filter_address, callback_webhook } = args;
|
||||
|
||||
if (!event_types?.length) {
|
||||
throw new Error('event_types is required for subscription');
|
||||
}
|
||||
|
||||
this.logger.info(`Subscribing to events: ${event_types.join(', ')}`);
|
||||
|
||||
try {
|
||||
const subscription = await this.p2pConnector.subscribe({
|
||||
eventTypes: event_types,
|
||||
filterAddress: filter_address,
|
||||
callbackWebhook: callback_webhook,
|
||||
subscriberId: this.p2pConnector.getNodeId(),
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
subscription_id: subscription.id,
|
||||
event_types,
|
||||
filter_address,
|
||||
callback_webhook,
|
||||
subscribed_at: new Date().toISOString(),
|
||||
status: 'active',
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to create subscription:', error);
|
||||
throw new Error(`Subscription failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
private parseSemanticAddress(address: string): SemanticAddress {
|
||||
// Parse bzzz://agent:role@project:task/path
|
||||
const bzzzMatch = address.match(/^bzzz:\/\/([^@\/]+)@([^\/]+)(?:\/(.+))?$/);
|
||||
|
||||
if (bzzzMatch) {
|
||||
const [, agentRole, projectTask, path] = bzzzMatch;
|
||||
const [agent, role] = agentRole.split(':');
|
||||
const [project, task] = projectTask.split(':');
|
||||
|
||||
return {
|
||||
agent: agent !== '*' ? agent : undefined,
|
||||
role: role !== '*' ? role : undefined,
|
||||
project: project !== '*' ? project : undefined,
|
||||
task: task !== '*' ? task : undefined,
|
||||
path: path || undefined,
|
||||
raw: address,
|
||||
};
|
||||
}
|
||||
|
||||
// Simple address format
|
||||
return { raw: address };
|
||||
}
|
||||
|
||||
private async discoverAgents(address: SemanticAddress, criteria: any): Promise<any[]> {
|
||||
const agents = await this.agentManager.getAgents();
|
||||
|
||||
return agents.filter(agent => {
|
||||
if (address.agent && agent.id !== address.agent) return false;
|
||||
if (address.role && agent.role !== address.role) return false;
|
||||
if (criteria.availability && !agent.available) return false;
|
||||
if (criteria.performance_threshold && agent.performance < criteria.performance_threshold) return false;
|
||||
if (criteria.expertise?.length && !criteria.expertise.some((exp: string) =>
|
||||
agent.capabilities.includes(exp))) return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private async queryP2PNetwork(address: SemanticAddress): Promise<any[]> {
|
||||
// Query the P2P network for matching agents
|
||||
const query = {
|
||||
type: 'agent_discovery',
|
||||
criteria: address,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const responses = await this.p2pConnector.queryNetwork(query, 5000); // 5 second timeout
|
||||
return responses;
|
||||
}
|
||||
|
||||
private rankMatches(matches: any[], address: SemanticAddress, criteria: any): any[] {
|
||||
return matches
|
||||
.map(match => ({
|
||||
...match,
|
||||
score: this.calculateMatchScore(match, address, criteria),
|
||||
}))
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, 20); // Limit to top 20 matches
|
||||
}
|
||||
|
||||
private calculateMatchScore(match: any, address: SemanticAddress, criteria: any): number {
|
||||
let score = 0;
|
||||
|
||||
// Exact matches get highest score
|
||||
if (address.agent && match.id === address.agent) score += 100;
|
||||
if (address.role && match.role === address.role) score += 50;
|
||||
|
||||
// Capability matching
|
||||
if (criteria.expertise?.length) {
|
||||
const matchingExp = criteria.expertise.filter((exp: string) =>
|
||||
match.capabilities?.includes(exp)
|
||||
).length;
|
||||
score += (matchingExp / criteria.expertise.length) * 30;
|
||||
}
|
||||
|
||||
// Availability bonus
|
||||
if (match.available) score += 10;
|
||||
|
||||
// Performance bonus
|
||||
if (match.performance) score += match.performance * 10;
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
private async getAgentContent(address: SemanticAddress, maxHistory: number): Promise<any> {
|
||||
const agent = await this.agentManager.getAgent(address.agent!);
|
||||
if (!agent) {
|
||||
throw new Error(`Agent ${address.agent} not found`);
|
||||
}
|
||||
|
||||
const content = {
|
||||
agent_info: agent,
|
||||
recent_activity: await this.agentManager.getRecentActivity(address.agent!, maxHistory),
|
||||
current_tasks: await this.agentManager.getCurrentTasks(address.agent!),
|
||||
};
|
||||
|
||||
if (address.path) {
|
||||
content[address.path] = await this.agentManager.getAgentData(address.agent!, address.path);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private async getProjectContent(address: SemanticAddress, maxHistory: number): Promise<any> {
|
||||
// Get project-related content from P2P network
|
||||
return await this.p2pConnector.getProjectData(address.project!, maxHistory);
|
||||
}
|
||||
|
||||
private async getNetworkContent(address: SemanticAddress, maxHistory: number): Promise<any> {
|
||||
// Get general network content
|
||||
return await this.p2pConnector.getNetworkData(address.raw, maxHistory);
|
||||
}
|
||||
|
||||
private async getAgentMetadata(agentId: string): Promise<any> {
|
||||
return await this.agentManager.getAgentMetadata(agentId);
|
||||
}
|
||||
|
||||
private async getProjectMetadata(projectId: string): Promise<any> {
|
||||
return await this.p2pConnector.getProjectMetadata(projectId);
|
||||
}
|
||||
|
||||
private async postToAgent(agentId: string, message: any): Promise<any> {
|
||||
return await this.p2pConnector.sendDirectMessage(agentId, message);
|
||||
}
|
||||
|
||||
private async postToRole(role: string, message: any): Promise<any> {
|
||||
const topic = `bzzz/roles/${role.toLowerCase().replace(/\s+/g, '_')}/v1`;
|
||||
return await this.p2pConnector.publishMessage(topic, message);
|
||||
}
|
||||
|
||||
private async postToProject(projectId: string, message: any): Promise<any> {
|
||||
const topic = `bzzz/projects/${projectId}/coordination/v1`;
|
||||
return await this.p2pConnector.publishMessage(topic, message);
|
||||
}
|
||||
|
||||
private async postToNetwork(message: any): Promise<any> {
|
||||
return await this.p2pConnector.publishMessage('bzzz/coordination/v1', message);
|
||||
}
|
||||
|
||||
private generateMessageId(): string {
|
||||
return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user