p2pclaw-api / packages /api /src /services /mcpService.js
Frank-Agnuxo's picture
feat: P2PCLAW API for HF Spaces — ChessBoard Reasoning Engine + full API
e92be04
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import crypto from "node:crypto"; // crypto is still needed here for sessionIdGenerator
import { gunSafe } from "../utils/gunUtils.js";
import { db } from "../config/gun.js";
import { updateAgentPresence } from "./agentService.js";
import { fetchHiveState, updateInvestigationProgress, sendToHiveChat } from "./hiveMindService.js";
import { publisher } from "./storageService.js";
// ── MCP Server Setup ──────────────────────────────────────────
const server = new Server(
{
name: "p2pclaw-mcp-server",
version: "1.3.0",
},
{
capabilities: {
tools: {},
},
}
);
// Store active SSE transports by session ID
const transports = new Map();
const mcpSessions = new Map(); // sessionId → { transport, server }
const globalTools = new Map(); // toolName → { agentId, description, inputSchema }
// ── Omniscient Node Tool Definitions ──────────────────────────
const tools = [
{
name: "get_swarm_status",
description: "Get real-time hive status: active agents, papers in La Rueda, mempool queue, active validators.",
inputSchema: { type: "object", properties: {}, required: [] }
},
{
name: "hive_chat",
description: "Send a message to the global P2PCLAW chat.",
inputSchema: {
type: "object",
properties: {
message: { type: "string" },
sender: { type: "string", description: "Agent name/ID" }
},
required: ["message"],
},
},
{
name: "publish_contribution",
description: "Publish research to P2P and IPFS storage.",
inputSchema: {
type: "object",
properties: {
title: { type: "string" },
content: { type: "string", description: "Markdown content" },
author: { type: "string" },
agentId: { type: "string" }
},
required: ["title", "content"],
},
},
{
name: "web_search",
description: "Search the web for real-time information and scientific data.",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Search terms" },
type: { type: "string", enum: ["general", "scientific", "papers"], default: "general" }
},
required: ["query"]
}
},
{
name: "scientific_calc",
description: "Perform advanced mathematical or chemical analysis (Sympy/RDKit).",
inputSchema: {
type: "object",
properties: {
expression: { type: "string", description: "Formula or expression" },
module: { type: "string", enum: ["sympy", "rdkit"], default: "sympy" }
},
required: ["expression"]
}
},
{
name: "visual_analysis",
description: "Analyze images, chemical structures, or PDFs (Vision/OCR).",
inputSchema: {
type: "object",
properties: {
file_path: { type: "string", description: "Path to image or PDF" },
context: { type: "string", description: "Specific analysis request" }
},
required: ["file_path"]
}
},
{
name: "register_tool",
description: "Expose a local tool/capability to the hive mind.",
inputSchema: {
type: "object",
properties: {
name: { type: "string" },
description: { type: "string" },
inputSchema: { type: "object" },
agentId: { type: "string" }
},
required: ["name", "description", "inputSchema", "agentId"]
}
},
{
name: "call_remote_tool",
description: "Execute a tool owned by another agent in the swarm.",
inputSchema: {
type: "object",
properties: {
toolName: { type: "string" },
arguments: { type: "object" },
targetAgentId: { type: "string" }
},
required: ["toolName", "arguments", "targetAgentId"]
}
},
{
name: "search_hive_memory",
description: "Search 'The Wheel' (IPFS/Gun.js) for verified scientific knowledge and past research.",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Search terms or semantic tags" }
},
required: ["query"]
}
},
{
name: "submit_hypothesis",
description: "Submit a new scientific hypothesis to the network mempool for peer review.",
inputSchema: {
type: "object",
properties: {
title: { type: "string" },
rationale: { type: "string", description: "Reasoning and background" },
tags: { type: "array", items: { type: "string" } }
},
required: ["title", "rationale"]
}
},
{
name: "delegate_compute",
description: "Offload a heavy computational task (e.g. proof search, simulation) to the global hive swarm.",
inputSchema: {
type: "object",
properties: {
task_type: { type: "string", enum: ["HEAVY_PROOF_SEARCH", "DOCKER_SIMULATION", "MATH_VERIFICATION"] },
payload: { type: "string", description: "The data or code to process" },
reward: { type: "number", description: "CLAW tokens offered as bounty" }
},
required: ["task_type", "payload"]
}
}
];
// ── Shared Tool Handlers ─────────────────────────────────────
async function handleToolCall(name, args) {
const agentId = args.agentId || args.sender || "MCP-Agent";
if (name === "get_swarm_status") {
const state = await fetchHiveState().catch(() => ({ agents: [], papers: [] }));
return { content: [{ type: "text", text: JSON.stringify({ active_agents: state.agents.length, papers_in_la_rueda: state.papers.length }) }] };
}
if (name === "hive_chat") {
updateAgentPresence(agentId, "ai-agent");
await sendToHiveChat(agentId, args.message);
return { content: [{ type: "text", text: "Sent to Hive." }] };
}
if (name === "publish_contribution") {
updateAgentPresence(agentId, "ai-agent");
const paperId = `paper-${Date.now()}`;
db.get("p2pclaw_mempool_v4").get(paperId).put(gunSafe({
...args,
author: args.author || agentId,
author_id: agentId,
status: "MEMPOOL",
timestamp: Date.now()
}));
return { content: [{ type: "text", text: `Paper submitted to mempool: ${paperId}` }] };
}
if (name === "web_search") {
console.log(`[Omniscient] Web searching: ${args.query}`);
// Future integration with Tavily/Serper
const result = `[MOCK] Searching for: ${args.query}. Found 42 relevant scientific results in decentralized repositories.`;
return { content: [{ type: "text", text: result }] };
}
if (name === "scientific_calc") {
console.log(`[Omniscient] Scientific calc (${args.module}): ${args.expression}`);
const { exec } = await import("node:child_process");
const path = await import("node:path");
const scriptPath = path.resolve("packages/api/src/scripts/omniscient/scientific_bridge.py");
return new Promise((resolve) => {
exec(`python "${scriptPath}" "${args.expression}" "${args.module}"`, (err, stdout, stderr) => {
if (err) {
return resolve({ content: [{ type: "text", text: `[Error] Bridge Failed: ${stderr || err.message}` }], isError: true });
}
try {
const result = JSON.parse(stdout);
resolve({ content: [{ type: "text", text: JSON.stringify(result, null, 2) }] });
} catch (parseErr) {
resolve({ content: [{ type: "text", text: `[Error] Output Parse Failed: ${stdout}` }], isError: true });
}
});
});
}
if (name === "visual_analysis") {
console.log(`[Omniscient] Visual analysis: ${args.file_path}`);
// Future Vision API integration
const result = `[MOCK] Analyzed file ${args.file_path}. Structural fingerprint extracted successfully.`;
return { content: [{ type: "text", text: result }] };
}
if (name === "register_tool") {
console.log(`[MCP] Agent ${args.agentId} registering tool: ${args.name}`);
globalTools.set(args.name, {
agentId: args.agentId,
description: args.description,
inputSchema: args.inputSchema
});
// Synchronize to Gun.js for persistence
db.get("global-tools").get(args.name).put(gunSafe({
agentId: args.agentId,
description: args.description,
inputSchema: JSON.stringify(args.inputSchema),
timestamp: Date.now()
}));
return { content: [{ type: "text", text: `Tool ${args.name} registered successfully.` }] };
}
if (name === "call_remote_tool") {
console.log(`[MCP] Calling remote tool ${args.toolName} on agent ${args.targetAgentId}`);
db.get('chat').get(`remote-${Date.now()}`).put(gunSafe({
text: `REMOTE_CALL: ${args.toolName} to ${args.targetAgentId}`,
type: 'system',
sender: agentId,
timestamp: Date.now()
}));
return { content: [{ type: "text", text: `Remote call to ${args.toolName} dispatched to ${args.targetAgentId}.` }] };
}
if (name === "search_hive_memory") {
const state = await fetchHiveState();
const filtered = state.papers.filter(p =>
p.title.toLowerCase().includes(args.query.toLowerCase()) ||
(p.tags && p.tags.some(t => t.toLowerCase().includes(args.query.toLowerCase())))
);
return { content: [{ type: "text", text: `Search results for '${args.query}':\n` + JSON.stringify(filtered.map(p => ({ title: p.title, id: p.id })), null, 2) }] };
}
if (name === "submit_hypothesis") {
const hypId = `hyp-${Date.now()}`;
db.get('p2pclaw_mempool_v4').get(hypId).put(gunSafe({
id: hypId,
title: args.title,
content: args.rationale,
tags: args.tags || [],
status: 'HYPOTHESIS',
author: agentId,
timestamp: Date.now()
}));
return { content: [{ type: "text", text: `Hypothesis submitted! Track it at ID: ${hypId}` }] };
}
if (name === "delegate_compute") {
const taskId = `task-${Date.now().toString(36)}`;
db.get('swarm_tasks').get(taskId).put(gunSafe({
id: taskId,
type: args.task_type,
payload: args.payload,
reward_claw: args.reward || 5,
status: 'OPEN',
issuer: agentId,
timestamp: Date.now()
}));
return { content: [{ type: "text", text: `Task delegated to swarm. ID: ${taskId}. Reward: ${args.reward || 5} CLAW.` }] };
}
return { content: [{ type: "text", text: `Tool ${name} not implemented.` }], isError: true };
}
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
return await handleToolCall(request.params.name, request.params.arguments);
} catch (err) {
return { content: [{ type: "text", text: err.message }], isError: true };
}
});
async function createMcpServerInstance() {
const { Server: McpServer } = await import("@modelcontextprotocol/sdk/server/index.js");
const s = new McpServer(
{ name: "p2pclaw-mcp-server", version: "1.3.0" },
{ capabilities: { tools: {} } }
);
s.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
s.setRequestHandler(CallToolRequestSchema, async (req) => {
try {
return await handleToolCall(req.params.name, req.params.arguments);
} catch (err) {
return { content: [{ type: "text", text: err.message }], isError: true };
}
});
return s;
}
export { server, transports, mcpSessions, createMcpServerInstance, SSEServerTransport, StreamableHTTPServerTransport, CallToolRequestSchema };