405 lines
13 KiB
JavaScript
405 lines
13 KiB
JavaScript
/**
|
|
* P2P Swarm V2 MCP Tools
|
|
*
|
|
* Production-grade P2P swarm coordination exposed as MCP tools.
|
|
* Provides decentralized coordination, task execution, and learning sync.
|
|
*
|
|
* Features:
|
|
* - Ed25519/X25519 cryptography
|
|
* - GunDB relay coordination
|
|
* - Task execution with claim resolution
|
|
* - Heartbeat-based liveness
|
|
* - Verified member registry
|
|
*/
|
|
import { z } from 'zod';
|
|
import { createP2PSwarmV2 } from '../../../../swarm/p2p-swarm-v2.js';
|
|
import { logger } from '../../../../utils/logger.js';
|
|
// Global swarm instance for MCP tools
|
|
let swarmInstance = null;
|
|
/**
|
|
* Get or create swarm instance
|
|
*/
|
|
async function getSwarm(agentId, swarmKey) {
|
|
if (!swarmInstance) {
|
|
const id = agentId || `mcp-agent-${Date.now().toString(36)}`;
|
|
swarmInstance = await createP2PSwarmV2(id, swarmKey);
|
|
}
|
|
return swarmInstance;
|
|
}
|
|
/**
|
|
* P2P Swarm Connect Tool
|
|
*/
|
|
export const p2pSwarmConnectTool = {
|
|
name: 'p2p_swarm_connect',
|
|
description: 'Connect to a P2P swarm for decentralized coordination',
|
|
schema: z.object({
|
|
agentId: z.string().optional().describe('Agent ID (auto-generated if not provided)'),
|
|
swarmKey: z.string().optional().describe('Swarm key to join existing swarm'),
|
|
enableExecutor: z.boolean().optional().default(false).describe('Enable task executor'),
|
|
}),
|
|
execute: async (params) => {
|
|
try {
|
|
const swarm = await getSwarm(params.agentId, params.swarmKey);
|
|
if (params.enableExecutor) {
|
|
swarm.startTaskExecutor();
|
|
}
|
|
const status = swarm.getStatus();
|
|
return {
|
|
success: true,
|
|
connected: status.connected,
|
|
agentId: status.agentId,
|
|
swarmId: status.swarmId,
|
|
swarmKey: swarm.getSwarmKey(),
|
|
relays: status.relays,
|
|
executorEnabled: params.enableExecutor || false,
|
|
};
|
|
}
|
|
catch (error) {
|
|
logger.error('Failed to connect to swarm', { error });
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
};
|
|
}
|
|
},
|
|
};
|
|
/**
|
|
* P2P Swarm Status Tool
|
|
*/
|
|
export const p2pSwarmStatusTool = {
|
|
name: 'p2p_swarm_status',
|
|
description: 'Get current P2P swarm status including live members',
|
|
schema: z.object({}),
|
|
execute: async () => {
|
|
if (!swarmInstance) {
|
|
return {
|
|
success: false,
|
|
error: 'Not connected to swarm. Use p2p_swarm_connect first.',
|
|
};
|
|
}
|
|
const status = swarmInstance.getStatus();
|
|
const liveMembers = swarmInstance.getLiveMembers();
|
|
return {
|
|
success: true,
|
|
...status,
|
|
liveMembers,
|
|
liveMemberCount: swarmInstance.getLiveMemberCount(),
|
|
};
|
|
},
|
|
};
|
|
/**
|
|
* P2P Swarm Members Tool
|
|
*/
|
|
export const p2pSwarmMembersTool = {
|
|
name: 'p2p_swarm_members',
|
|
description: 'List swarm members with their capabilities and liveness',
|
|
schema: z.object({
|
|
includeOffline: z.boolean().optional().default(false).describe('Include offline members'),
|
|
}),
|
|
execute: async (params) => {
|
|
if (!swarmInstance) {
|
|
return {
|
|
success: false,
|
|
error: 'Not connected to swarm. Use p2p_swarm_connect first.',
|
|
};
|
|
}
|
|
const members = swarmInstance.getLiveMembers();
|
|
const filtered = params.includeOffline ? members : members.filter(m => m.isAlive);
|
|
return {
|
|
success: true,
|
|
count: filtered.length,
|
|
liveCount: filtered.filter(m => m.isAlive).length,
|
|
members: filtered.map(m => ({
|
|
agentId: m.agentId,
|
|
capabilities: m.capabilities,
|
|
isAlive: m.isAlive,
|
|
lastSeenAgo: Math.round((Date.now() - m.lastSeen) / 1000),
|
|
})),
|
|
};
|
|
},
|
|
};
|
|
/**
|
|
* P2P Swarm Publish Tool
|
|
*/
|
|
export const p2pSwarmPublishTool = {
|
|
name: 'p2p_swarm_publish',
|
|
description: 'Publish a signed and encrypted message to a swarm topic',
|
|
schema: z.object({
|
|
topic: z.string().describe('Topic to publish to'),
|
|
payload: z.any().describe('Message payload (will be JSON serialized)'),
|
|
}),
|
|
execute: async (params) => {
|
|
if (!swarmInstance) {
|
|
return {
|
|
success: false,
|
|
error: 'Not connected to swarm. Use p2p_swarm_connect first.',
|
|
};
|
|
}
|
|
try {
|
|
const messageId = await swarmInstance.publish(params.topic, params.payload);
|
|
return {
|
|
success: true,
|
|
messageId,
|
|
topic: params.topic,
|
|
};
|
|
}
|
|
catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
};
|
|
}
|
|
},
|
|
};
|
|
/**
|
|
* P2P Swarm Subscribe Tool
|
|
*/
|
|
export const p2pSwarmSubscribeTool = {
|
|
name: 'p2p_swarm_subscribe',
|
|
description: 'Subscribe to a swarm topic (messages delivered via callback)',
|
|
schema: z.object({
|
|
topic: z.string().describe('Topic to subscribe to'),
|
|
}),
|
|
execute: async (params) => {
|
|
if (!swarmInstance) {
|
|
return {
|
|
success: false,
|
|
error: 'Not connected to swarm. Use p2p_swarm_connect first.',
|
|
};
|
|
}
|
|
// Note: In MCP context, we return success but messages are handled internally
|
|
// For real-time messages, use the streaming MCP transport
|
|
swarmInstance.subscribe(params.topic, (data, from) => {
|
|
logger.info('Swarm message received', { topic: params.topic, from, data });
|
|
});
|
|
return {
|
|
success: true,
|
|
subscribed: params.topic,
|
|
note: 'Messages will be logged. Use streaming transport for real-time delivery.',
|
|
};
|
|
},
|
|
};
|
|
/**
|
|
* P2P Swarm Sync Q-Table Tool
|
|
*/
|
|
export const p2pSwarmSyncQTableTool = {
|
|
name: 'p2p_swarm_sync_qtable',
|
|
description: 'Sync a Q-table to the swarm for distributed learning',
|
|
schema: z.object({
|
|
qTable: z.array(z.array(z.number())).describe('2D array Q-table'),
|
|
}),
|
|
execute: async (params) => {
|
|
if (!swarmInstance) {
|
|
return {
|
|
success: false,
|
|
error: 'Not connected to swarm. Use p2p_swarm_connect first.',
|
|
};
|
|
}
|
|
try {
|
|
const pointer = await swarmInstance.syncQTable(params.qTable);
|
|
return {
|
|
success: true,
|
|
cid: pointer.cid,
|
|
dimensions: pointer.dimensions,
|
|
checksum: pointer.checksum,
|
|
timestamp: pointer.timestamp,
|
|
};
|
|
}
|
|
catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
};
|
|
}
|
|
},
|
|
};
|
|
/**
|
|
* P2P Swarm Sync Memory Tool
|
|
*/
|
|
export const p2pSwarmSyncMemoryTool = {
|
|
name: 'p2p_swarm_sync_memory',
|
|
description: 'Sync memory vectors to the swarm',
|
|
schema: z.object({
|
|
vectors: z.array(z.array(z.number())).describe('2D array of vectors'),
|
|
namespace: z.string().optional().default('default').describe('Memory namespace'),
|
|
}),
|
|
execute: async (params) => {
|
|
if (!swarmInstance) {
|
|
return {
|
|
success: false,
|
|
error: 'Not connected to swarm. Use p2p_swarm_connect first.',
|
|
};
|
|
}
|
|
try {
|
|
const pointer = await swarmInstance.syncMemory(params.vectors, params.namespace || 'default');
|
|
return {
|
|
success: true,
|
|
cid: pointer.cid,
|
|
namespace: params.namespace || 'default',
|
|
dimensions: pointer.dimensions,
|
|
checksum: pointer.checksum,
|
|
};
|
|
}
|
|
catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
};
|
|
}
|
|
},
|
|
};
|
|
/**
|
|
* P2P Swarm Submit Task Tool
|
|
*/
|
|
export const p2pSwarmSubmitTaskTool = {
|
|
name: 'p2p_swarm_submit_task',
|
|
description: 'Submit a task for distributed execution',
|
|
schema: z.object({
|
|
moduleCID: z.string().describe('CID of WASM module'),
|
|
inputCID: z.string().describe('CID of input data'),
|
|
entrypoint: z.string().optional().default('main').describe('Entrypoint function'),
|
|
fuelLimit: z.number().optional().default(1000000).describe('Fuel limit'),
|
|
memoryMB: z.number().optional().default(64).describe('Memory limit in MB'),
|
|
timeoutMs: z.number().optional().default(30000).describe('Timeout in ms'),
|
|
}),
|
|
execute: async (params) => {
|
|
if (!swarmInstance) {
|
|
return {
|
|
success: false,
|
|
error: 'Not connected to swarm. Use p2p_swarm_connect first.',
|
|
};
|
|
}
|
|
try {
|
|
const taskId = `task-${Date.now().toString(36)}`;
|
|
const messageId = await swarmInstance.submitTask({
|
|
taskId,
|
|
moduleCID: params.moduleCID,
|
|
inputCID: params.inputCID,
|
|
entrypoint: params.entrypoint || 'main',
|
|
outputSchemaHash: '',
|
|
budgets: {
|
|
fuelLimit: params.fuelLimit || 1000000,
|
|
memoryMB: params.memoryMB || 64,
|
|
timeoutMs: params.timeoutMs || 30000,
|
|
},
|
|
});
|
|
return {
|
|
success: true,
|
|
taskId,
|
|
messageId,
|
|
moduleCID: params.moduleCID,
|
|
inputCID: params.inputCID,
|
|
};
|
|
}
|
|
catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
};
|
|
}
|
|
},
|
|
};
|
|
/**
|
|
* P2P Swarm Start Executor Tool
|
|
*/
|
|
export const p2pSwarmStartExecutorTool = {
|
|
name: 'p2p_swarm_start_executor',
|
|
description: 'Start the task executor to process distributed tasks',
|
|
schema: z.object({}),
|
|
execute: async () => {
|
|
if (!swarmInstance) {
|
|
return {
|
|
success: false,
|
|
error: 'Not connected to swarm. Use p2p_swarm_connect first.',
|
|
};
|
|
}
|
|
swarmInstance.startTaskExecutor();
|
|
return {
|
|
success: true,
|
|
message: 'Task executor started. Listening for tasks.',
|
|
};
|
|
},
|
|
};
|
|
/**
|
|
* P2P Swarm Stop Executor Tool
|
|
*/
|
|
export const p2pSwarmStopExecutorTool = {
|
|
name: 'p2p_swarm_stop_executor',
|
|
description: 'Stop the task executor',
|
|
schema: z.object({}),
|
|
execute: async () => {
|
|
if (!swarmInstance) {
|
|
return {
|
|
success: false,
|
|
error: 'Not connected to swarm.',
|
|
};
|
|
}
|
|
swarmInstance.stopTaskExecutor();
|
|
return {
|
|
success: true,
|
|
message: 'Task executor stopped.',
|
|
};
|
|
},
|
|
};
|
|
/**
|
|
* P2P Swarm Disconnect Tool
|
|
*/
|
|
export const p2pSwarmDisconnectTool = {
|
|
name: 'p2p_swarm_disconnect',
|
|
description: 'Disconnect from the P2P swarm',
|
|
schema: z.object({}),
|
|
execute: async () => {
|
|
if (swarmInstance) {
|
|
swarmInstance.disconnect();
|
|
swarmInstance = null;
|
|
return {
|
|
success: true,
|
|
message: 'Disconnected from swarm.',
|
|
};
|
|
}
|
|
return {
|
|
success: true,
|
|
message: 'Not connected to swarm.',
|
|
};
|
|
},
|
|
};
|
|
/**
|
|
* P2P Swarm Keygen Tool
|
|
*/
|
|
export const p2pSwarmKeygenTool = {
|
|
name: 'p2p_swarm_keygen',
|
|
description: 'Generate a new swarm key for creating a new swarm',
|
|
schema: z.object({}),
|
|
execute: async () => {
|
|
const crypto = await import('crypto');
|
|
const key = crypto.randomBytes(32).toString('base64');
|
|
return {
|
|
success: true,
|
|
swarmKey: key,
|
|
usage: `Use with p2p_swarm_connect: { swarmKey: "${key}" }`,
|
|
};
|
|
},
|
|
};
|
|
// Export all tools as array for MCP registration
|
|
export const p2pSwarmTools = [
|
|
p2pSwarmConnectTool,
|
|
p2pSwarmStatusTool,
|
|
p2pSwarmMembersTool,
|
|
p2pSwarmPublishTool,
|
|
p2pSwarmSubscribeTool,
|
|
p2pSwarmSyncQTableTool,
|
|
p2pSwarmSyncMemoryTool,
|
|
p2pSwarmSubmitTaskTool,
|
|
p2pSwarmStartExecutorTool,
|
|
p2pSwarmStopExecutorTool,
|
|
p2pSwarmDisconnectTool,
|
|
p2pSwarmKeygenTool,
|
|
];
|
|
// Export singleton accessor
|
|
export function getP2PSwarmInstance() {
|
|
return swarmInstance;
|
|
}
|
|
export function setP2PSwarmInstance(instance) {
|
|
swarmInstance = instance;
|
|
}
|
|
//# sourceMappingURL=p2p-swarm-tools.js.map
|