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:
303
mcp-server/src/config/config.ts
Normal file
303
mcp-server/src/config/config.ts
Normal file
@@ -0,0 +1,303 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export interface BzzzMcpConfig {
|
||||
openai: {
|
||||
apiKey: string;
|
||||
defaultModel: string;
|
||||
maxTokens: number;
|
||||
temperature: number;
|
||||
};
|
||||
bzzz: {
|
||||
nodeUrl: string;
|
||||
networkId: string;
|
||||
pubsubTopics: string[];
|
||||
};
|
||||
cost: {
|
||||
dailyLimit: number;
|
||||
monthlyLimit: number;
|
||||
warningThreshold: number;
|
||||
};
|
||||
conversation: {
|
||||
maxActiveThreads: number;
|
||||
defaultTimeout: number;
|
||||
escalationRules: EscalationRule[];
|
||||
};
|
||||
agents: {
|
||||
maxAgents: number;
|
||||
defaultRoles: AgentRoleConfig[];
|
||||
};
|
||||
logging: {
|
||||
level: string;
|
||||
file?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface EscalationRule {
|
||||
name: string;
|
||||
conditions: EscalationCondition[];
|
||||
actions: EscalationAction[];
|
||||
priority: number;
|
||||
}
|
||||
|
||||
export interface EscalationCondition {
|
||||
type: 'thread_duration' | 'no_progress' | 'disagreement_count' | 'error_rate';
|
||||
threshold: number | boolean;
|
||||
timeframe?: number; // seconds
|
||||
}
|
||||
|
||||
export interface EscalationAction {
|
||||
type: 'notify_human' | 'request_expert' | 'escalate_to_architect' | 'create_decision_thread';
|
||||
target?: string;
|
||||
priority?: string;
|
||||
participants?: string[];
|
||||
}
|
||||
|
||||
export interface AgentRoleConfig {
|
||||
role: string;
|
||||
capabilities: string[];
|
||||
systemPrompt: string;
|
||||
interactionPatterns: Record<string, string>;
|
||||
specialization: string;
|
||||
}
|
||||
|
||||
export class Config {
|
||||
private static instance: Config;
|
||||
private config: BzzzMcpConfig;
|
||||
|
||||
private constructor() {
|
||||
this.config = this.loadConfig();
|
||||
}
|
||||
|
||||
public static getInstance(): Config {
|
||||
if (!Config.instance) {
|
||||
Config.instance = new Config();
|
||||
}
|
||||
return Config.instance;
|
||||
}
|
||||
|
||||
public get openai() {
|
||||
return this.config.openai;
|
||||
}
|
||||
|
||||
public get bzzz() {
|
||||
return this.config.bzzz;
|
||||
}
|
||||
|
||||
public get cost() {
|
||||
return this.config.cost;
|
||||
}
|
||||
|
||||
public get conversation() {
|
||||
return this.config.conversation;
|
||||
}
|
||||
|
||||
public get agents() {
|
||||
return this.config.agents;
|
||||
}
|
||||
|
||||
public get logging() {
|
||||
return this.config.logging;
|
||||
}
|
||||
|
||||
private loadConfig(): BzzzMcpConfig {
|
||||
// Load OpenAI API key from BZZZ secrets
|
||||
const openaiKeyPath = path.join(
|
||||
process.env.HOME || '/home/tony',
|
||||
'chorus/business/secrets/openai-api-key-for-bzzz.txt'
|
||||
);
|
||||
|
||||
let openaiKey = process.env.OPENAI_API_KEY || '';
|
||||
try {
|
||||
openaiKey = readFileSync(openaiKeyPath, 'utf8').trim();
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load OpenAI key from ${openaiKeyPath}:`, error);
|
||||
}
|
||||
|
||||
const defaultConfig: BzzzMcpConfig = {
|
||||
openai: {
|
||||
apiKey: openaiKey,
|
||||
defaultModel: process.env.OPENAI_MODEL || 'gpt-4',
|
||||
maxTokens: parseInt(process.env.OPENAI_MAX_TOKENS || '4000'),
|
||||
temperature: parseFloat(process.env.OPENAI_TEMPERATURE || '0.7'),
|
||||
},
|
||||
bzzz: {
|
||||
nodeUrl: process.env.BZZZ_NODE_URL || 'http://localhost:8080',
|
||||
networkId: process.env.BZZZ_NETWORK_ID || 'bzzz-local',
|
||||
pubsubTopics: [
|
||||
'bzzz/coordination/v1',
|
||||
'hmmm/meta-discussion/v1',
|
||||
'bzzz/context-feedback/v1'
|
||||
],
|
||||
},
|
||||
cost: {
|
||||
dailyLimit: parseFloat(process.env.DAILY_COST_LIMIT || '100.0'),
|
||||
monthlyLimit: parseFloat(process.env.MONTHLY_COST_LIMIT || '1000.0'),
|
||||
warningThreshold: parseFloat(process.env.COST_WARNING_THRESHOLD || '0.8'),
|
||||
},
|
||||
conversation: {
|
||||
maxActiveThreads: parseInt(process.env.MAX_ACTIVE_THREADS || '10'),
|
||||
defaultTimeout: parseInt(process.env.THREAD_TIMEOUT || '3600'), // 1 hour
|
||||
escalationRules: this.getDefaultEscalationRules(),
|
||||
},
|
||||
agents: {
|
||||
maxAgents: parseInt(process.env.MAX_AGENTS || '5'),
|
||||
defaultRoles: this.getDefaultAgentRoles(),
|
||||
},
|
||||
logging: {
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
file: process.env.LOG_FILE,
|
||||
},
|
||||
};
|
||||
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
private getDefaultEscalationRules(): EscalationRule[] {
|
||||
return [
|
||||
{
|
||||
name: 'Long Running Thread',
|
||||
priority: 1,
|
||||
conditions: [
|
||||
{
|
||||
type: 'thread_duration',
|
||||
threshold: 7200, // 2 hours
|
||||
timeframe: 0,
|
||||
},
|
||||
{
|
||||
type: 'no_progress',
|
||||
threshold: true,
|
||||
timeframe: 1800, // 30 minutes
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
type: 'notify_human',
|
||||
target: 'project_manager',
|
||||
priority: 'medium',
|
||||
},
|
||||
{
|
||||
type: 'request_expert',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Consensus Failure',
|
||||
priority: 2,
|
||||
conditions: [
|
||||
{
|
||||
type: 'disagreement_count',
|
||||
threshold: 3,
|
||||
timeframe: 0,
|
||||
},
|
||||
{
|
||||
type: 'thread_duration',
|
||||
threshold: 3600, // 1 hour
|
||||
timeframe: 0,
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
type: 'escalate_to_architect',
|
||||
priority: 'high',
|
||||
},
|
||||
{
|
||||
type: 'create_decision_thread',
|
||||
participants: ['senior_architect'],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private getDefaultAgentRoles(): AgentRoleConfig[] {
|
||||
return [
|
||||
{
|
||||
role: 'architect',
|
||||
specialization: 'system_design',
|
||||
capabilities: [
|
||||
'system_design',
|
||||
'architecture_review',
|
||||
'technology_selection',
|
||||
'scalability_analysis',
|
||||
],
|
||||
systemPrompt: `You are a senior software architect specializing in distributed systems and P2P networks.
|
||||
Your role is to provide technical guidance, review system designs, and ensure architectural consistency.
|
||||
You work collaboratively with other agents and can coordinate multi-agent discussions.
|
||||
|
||||
Available BZZZ tools allow you to:
|
||||
- Announce your presence and capabilities
|
||||
- Discover and communicate with other agents
|
||||
- Participate in threaded conversations
|
||||
- Post messages and updates to the P2P network
|
||||
- Subscribe to relevant events and notifications
|
||||
|
||||
Always consider:
|
||||
- System scalability and performance
|
||||
- Security implications
|
||||
- Maintainability and code quality
|
||||
- Integration with existing CHORUS infrastructure`,
|
||||
interactionPatterns: {
|
||||
'peer_architects': 'collaborative_review',
|
||||
'developers': 'guidance_provision',
|
||||
'reviewers': 'design_validation',
|
||||
},
|
||||
},
|
||||
{
|
||||
role: 'reviewer',
|
||||
specialization: 'code_quality',
|
||||
capabilities: [
|
||||
'code_review',
|
||||
'security_analysis',
|
||||
'performance_optimization',
|
||||
'best_practices_enforcement',
|
||||
],
|
||||
systemPrompt: `You are a senior code reviewer focused on maintaining high code quality and security standards.
|
||||
Your role is to review code changes, identify potential issues, and provide constructive feedback.
|
||||
You collaborate with developers and architects to ensure code meets quality standards.
|
||||
|
||||
When reviewing code, evaluate:
|
||||
- Code correctness and logic
|
||||
- Security vulnerabilities
|
||||
- Performance implications
|
||||
- Adherence to best practices
|
||||
- Test coverage and quality
|
||||
- Documentation completeness
|
||||
|
||||
Provide specific, actionable feedback and suggest improvements where needed.`,
|
||||
interactionPatterns: {
|
||||
'architects': 'design_consultation',
|
||||
'developers': 'feedback_provision',
|
||||
'other_reviewers': 'peer_review',
|
||||
},
|
||||
},
|
||||
{
|
||||
role: 'documentation',
|
||||
specialization: 'technical_writing',
|
||||
capabilities: [
|
||||
'technical_writing',
|
||||
'api_documentation',
|
||||
'user_guides',
|
||||
'knowledge_synthesis',
|
||||
],
|
||||
systemPrompt: `You specialize in creating clear, comprehensive technical documentation.
|
||||
Your role is to analyze technical content, identify documentation needs, and create high-quality documentation.
|
||||
You work with all team members to ensure knowledge is properly captured and shared.
|
||||
|
||||
Focus on:
|
||||
- Clarity and readability
|
||||
- Completeness and accuracy
|
||||
- Appropriate level of detail for the audience
|
||||
- Proper structure and organization
|
||||
- Integration with existing documentation
|
||||
|
||||
Consider different audiences: developers, users, administrators, and stakeholders.`,
|
||||
interactionPatterns: {
|
||||
'all_roles': 'information_gathering',
|
||||
'architects': 'technical_consultation',
|
||||
'developers': 'implementation_clarification',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
361
mcp-server/src/index.ts
Normal file
361
mcp-server/src/index.ts
Normal file
@@ -0,0 +1,361 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* BZZZ MCP Server
|
||||
* Model Context Protocol server enabling GPT-4 agents to participate in BZZZ P2P network
|
||||
*/
|
||||
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { BzzzProtocolTools } from "./tools/protocol-tools.js";
|
||||
import { AgentManager } from "./agents/agent-manager.js";
|
||||
import { ConversationManager } from "./conversations/conversation-manager.js";
|
||||
import { BzzzP2PConnector } from "./p2p/bzzz-connector.js";
|
||||
import { OpenAIIntegration } from "./ai/openai-integration.js";
|
||||
import { CostTracker } from "./utils/cost-tracker.js";
|
||||
import { Logger } from "./utils/logger.js";
|
||||
import { Config } from "./config/config.js";
|
||||
|
||||
class BzzzMcpServer {
|
||||
private server: Server;
|
||||
private protocolTools: BzzzProtocolTools;
|
||||
private agentManager: AgentManager;
|
||||
private conversationManager: ConversationManager;
|
||||
private p2pConnector: BzzzP2PConnector;
|
||||
private openaiIntegration: OpenAIIntegration;
|
||||
private costTracker: CostTracker;
|
||||
private logger: Logger;
|
||||
|
||||
constructor() {
|
||||
this.logger = new Logger("BzzzMcpServer");
|
||||
|
||||
// Initialize server
|
||||
this.server = new Server(
|
||||
{
|
||||
name: "bzzz-mcp-server",
|
||||
version: "1.0.0",
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {},
|
||||
resources: {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Initialize components
|
||||
this.initializeComponents();
|
||||
this.setupToolHandlers();
|
||||
this.setupEventHandlers();
|
||||
}
|
||||
|
||||
private initializeComponents(): void {
|
||||
const config = Config.getInstance();
|
||||
|
||||
// Initialize OpenAI integration
|
||||
this.openaiIntegration = new OpenAIIntegration({
|
||||
apiKey: config.openai.apiKey,
|
||||
defaultModel: config.openai.defaultModel,
|
||||
maxTokens: config.openai.maxTokens,
|
||||
});
|
||||
|
||||
// Initialize cost tracking
|
||||
this.costTracker = new CostTracker({
|
||||
dailyLimit: config.cost.dailyLimit,
|
||||
monthlyLimit: config.cost.monthlyLimit,
|
||||
warningThreshold: config.cost.warningThreshold,
|
||||
});
|
||||
|
||||
// Initialize P2P connector
|
||||
this.p2pConnector = new BzzzP2PConnector({
|
||||
bzzzNodeUrl: config.bzzz.nodeUrl,
|
||||
networkId: config.bzzz.networkId,
|
||||
});
|
||||
|
||||
// Initialize conversation manager
|
||||
this.conversationManager = new ConversationManager({
|
||||
maxActiveThreads: config.conversation.maxActiveThreads,
|
||||
defaultTimeout: config.conversation.defaultTimeout,
|
||||
escalationRules: config.conversation.escalationRules,
|
||||
});
|
||||
|
||||
// Initialize agent manager
|
||||
this.agentManager = new AgentManager({
|
||||
openaiIntegration: this.openaiIntegration,
|
||||
costTracker: this.costTracker,
|
||||
conversationManager: this.conversationManager,
|
||||
p2pConnector: this.p2pConnector,
|
||||
});
|
||||
|
||||
// Initialize protocol tools
|
||||
this.protocolTools = new BzzzProtocolTools({
|
||||
agentManager: this.agentManager,
|
||||
p2pConnector: this.p2pConnector,
|
||||
conversationManager: this.conversationManager,
|
||||
});
|
||||
}
|
||||
|
||||
private setupToolHandlers(): void {
|
||||
// List available tools
|
||||
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: [
|
||||
// Protocol tools
|
||||
{
|
||||
name: "bzzz_announce",
|
||||
description: "Announce agent presence and capabilities on the BZZZ network",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
agent_id: { type: "string", description: "Unique agent identifier" },
|
||||
role: { type: "string", description: "Agent role (architect, reviewer, etc.)" },
|
||||
capabilities: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "List of agent capabilities"
|
||||
},
|
||||
specialization: { type: "string", description: "Agent specialization area" },
|
||||
max_tasks: { type: "number", default: 3, description: "Maximum concurrent tasks" },
|
||||
},
|
||||
required: ["agent_id", "role"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bzzz_lookup",
|
||||
description: "Discover agents and resources using semantic addressing",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
semantic_address: {
|
||||
type: "string",
|
||||
description: "Format: bzzz://agent:role@project:task/path",
|
||||
},
|
||||
filter_criteria: {
|
||||
type: "object",
|
||||
properties: {
|
||||
expertise: { type: "array", items: { type: "string" } },
|
||||
availability: { type: "boolean" },
|
||||
performance_threshold: { type: "number" },
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ["semantic_address"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bzzz_get",
|
||||
description: "Retrieve content from BZZZ semantic addresses",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
address: { type: "string", description: "BZZZ semantic address" },
|
||||
include_metadata: { type: "boolean", default: true },
|
||||
max_history: { type: "number", default: 10 },
|
||||
},
|
||||
required: ["address"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bzzz_post",
|
||||
description: "Post events or messages to BZZZ addresses",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
target_address: { type: "string", description: "Target BZZZ address" },
|
||||
message_type: { type: "string", description: "Type of message" },
|
||||
content: { type: "object", description: "Message content" },
|
||||
priority: {
|
||||
type: "string",
|
||||
enum: ["low", "medium", "high", "urgent"],
|
||||
default: "medium"
|
||||
},
|
||||
thread_id: { type: "string", description: "Optional conversation thread ID" },
|
||||
},
|
||||
required: ["target_address", "message_type", "content"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bzzz_thread",
|
||||
description: "Manage threaded conversations between agents",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
action: {
|
||||
type: "string",
|
||||
enum: ["create", "join", "leave", "list", "summarize"],
|
||||
description: "Thread action to perform"
|
||||
},
|
||||
thread_id: { type: "string", description: "Thread identifier" },
|
||||
participants: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "List of participant agent IDs"
|
||||
},
|
||||
topic: { type: "string", description: "Thread topic" },
|
||||
},
|
||||
required: ["action"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bzzz_subscribe",
|
||||
description: "Subscribe to real-time events from BZZZ network",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
event_types: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "Types of events to subscribe to"
|
||||
},
|
||||
filter_address: { type: "string", description: "Optional address filter" },
|
||||
callback_webhook: { type: "string", description: "Optional webhook URL" },
|
||||
},
|
||||
required: ["event_types"],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
// Handle tool calls
|
||||
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const { name, arguments: args } = request.params;
|
||||
|
||||
try {
|
||||
let result;
|
||||
|
||||
switch (name) {
|
||||
case "bzzz_announce":
|
||||
result = await this.protocolTools.handleAnnounce(args);
|
||||
break;
|
||||
case "bzzz_lookup":
|
||||
result = await this.protocolTools.handleLookup(args);
|
||||
break;
|
||||
case "bzzz_get":
|
||||
result = await this.protocolTools.handleGet(args);
|
||||
break;
|
||||
case "bzzz_post":
|
||||
result = await this.protocolTools.handlePost(args);
|
||||
break;
|
||||
case "bzzz_thread":
|
||||
result = await this.protocolTools.handleThread(args);
|
||||
break;
|
||||
case "bzzz_subscribe":
|
||||
result = await this.protocolTools.handleSubscribe(args);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: JSON.stringify(result, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(`Tool execution failed for ${name}:`, error);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private setupEventHandlers(): void {
|
||||
// Handle P2P events
|
||||
this.p2pConnector.on("message", (message) => {
|
||||
this.logger.debug("P2P message received:", message);
|
||||
this.conversationManager.handleIncomingMessage(message);
|
||||
});
|
||||
|
||||
// Handle conversation events
|
||||
this.conversationManager.on("escalation", (thread, reason) => {
|
||||
this.logger.warn(`Thread ${thread.id} escalated: ${reason}`);
|
||||
this.handleEscalation(thread, reason);
|
||||
});
|
||||
|
||||
// Handle cost warnings
|
||||
this.costTracker.on("warning", (usage) => {
|
||||
this.logger.warn("Cost warning:", usage);
|
||||
});
|
||||
|
||||
this.costTracker.on("limit_exceeded", (usage) => {
|
||||
this.logger.error("Cost limit exceeded:", usage);
|
||||
// Implement emergency shutdown or throttling
|
||||
});
|
||||
}
|
||||
|
||||
private async handleEscalation(thread: any, reason: string): Promise<void> {
|
||||
// Implement human escalation logic
|
||||
this.logger.info(`Escalating thread ${thread.id} to human: ${reason}`);
|
||||
|
||||
// Could integrate with:
|
||||
// - Slack notifications
|
||||
// - Email alerts
|
||||
// - WHOOSH orchestration system
|
||||
// - N8N workflows
|
||||
}
|
||||
|
||||
public async start(): Promise<void> {
|
||||
// Connect to BZZZ P2P network
|
||||
await this.p2pConnector.connect();
|
||||
this.logger.info("Connected to BZZZ P2P network");
|
||||
|
||||
// Start conversation manager
|
||||
await this.conversationManager.start();
|
||||
this.logger.info("Conversation manager started");
|
||||
|
||||
// Start agent manager
|
||||
await this.agentManager.start();
|
||||
this.logger.info("Agent manager started");
|
||||
|
||||
// Start MCP server
|
||||
const transport = new StdioServerTransport();
|
||||
await this.server.connect(transport);
|
||||
this.logger.info("BZZZ MCP Server started and listening");
|
||||
}
|
||||
|
||||
public async stop(): Promise<void> {
|
||||
this.logger.info("Shutting down BZZZ MCP Server...");
|
||||
|
||||
await this.agentManager.stop();
|
||||
await this.conversationManager.stop();
|
||||
await this.p2pConnector.disconnect();
|
||||
|
||||
this.logger.info("BZZZ MCP Server stopped");
|
||||
}
|
||||
}
|
||||
|
||||
// Start server if run directly
|
||||
if (require.main === module) {
|
||||
const server = new BzzzMcpServer();
|
||||
|
||||
process.on("SIGINT", async () => {
|
||||
console.log("Received SIGINT, shutting down gracefully...");
|
||||
await server.stop();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on("SIGTERM", async () => {
|
||||
console.log("Received SIGTERM, shutting down gracefully...");
|
||||
await server.stop();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
server.start().catch((error) => {
|
||||
console.error("Failed to start BZZZ MCP Server:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export { BzzzMcpServer };
|
||||
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