Skip to main content
Glama

Physics MCP Server

by BlinkZer0
index.tsβ€’28.2 kB
#!/usr/bin/env node /** * Physics MCP Server * * Main server that orchestrates CAS, Plot, and NLI tools for physics computations. * Communicates via JSON-RPC over stdio with MCP clients. */ import 'dotenv/config'; // Type definitions for MCP SDK interface MCPTool { name: string; description: string; inputSchema: Record<string, unknown>; } interface MCPRequest { params: { name: string; arguments: Record<string, unknown>; }; } // Tool handler function types type ToolBuilder = () => MCPTool[]; type ToolHandler = (name: string, args: Record<string, unknown>) => Promise<Record<string, unknown>>; // Persistence Manager interface interface SessionEvent { tool_name: string; ts: number; input_json: string; output_json: string; } interface SessionArtifact { kind: string; path: string; } interface PersistenceManager { ensureSession(sessionId?: string): string; getSessionEvents(sessionId: string): SessionEvent[]; getSessionArtifacts(sessionId: string): SessionArtifact[]; getArtifactPath(sessionId: string, filename: string): string; recordArtifact(sessionId: string, kind: string, path: string, metadata: Record<string, unknown>): void; recordEvent(sessionId: string, toolName: string, input: Record<string, unknown>, output: Record<string, unknown>): void; } // MCP SDK components - will be set during initialization // These are kept as 'any' because the MCP SDK doesn't provide proper TypeScript types yet let Server: any; // eslint-disable-line @typescript-eslint/no-explicit-any let StdioServerTransport: any; // eslint-disable-line @typescript-eslint/no-explicit-any let CallToolRequestSchema: any; // eslint-disable-line @typescript-eslint/no-explicit-any let ListToolsRequestSchema: any; // eslint-disable-line @typescript-eslint/no-explicit-any let InitializeRequestSchema: any; // eslint-disable-line @typescript-eslint/no-explicit-any let getPersistenceManagerFn: (() => PersistenceManager) | undefined; // Tool handlers - these will be set during initialization let buildCASTools: ToolBuilder | undefined; let handleCASTool: ToolHandler | undefined; let buildPlotTools: ToolBuilder | undefined; let handlePlotTool: ToolHandler | undefined; let buildNLITools: ToolBuilder | undefined; let handleNLITool: ToolHandler | undefined; let buildUnitsTools: ToolBuilder | undefined; let handleUnitsTool: ToolHandler | undefined; let buildConstantsTools: ToolBuilder | undefined; let handleConstantsTool: ToolHandler | undefined; let buildReportTools: ToolBuilder | undefined; let buildTensorTools: ToolBuilder | undefined; let handleTensorTool: ToolHandler | undefined; let buildQuantumTools: ToolBuilder | undefined; let handleQuantumTool: ToolHandler | undefined; let buildStatmechTools: ToolBuilder | undefined; let handleStatmechTool: ToolHandler | undefined; // Phase 4 tool handlers let buildDataIOTools: ToolBuilder | undefined; let handleDataIOTool: ToolHandler | undefined; let buildExternalTools: ToolBuilder | undefined; let handleExternalTool: ToolHandler | undefined; let buildExportTools: ToolBuilder | undefined; let handleExportTool: ToolHandler | undefined; // Phase 6 ML tool handlers let buildMLTools: ToolBuilder | undefined; let handleMLAugmentationTool: ToolHandler | undefined; // Graphing Calculator tool handlers let buildGraphingCalculatorTools: ToolBuilder | undefined; let handleGraphingCalculatorTool: ToolHandler | undefined; // Phase 7 & 8 tool handlers let buildDistributedTools: ToolBuilder | undefined; let handleDistributedCollaborationTool: ToolHandler | undefined; let buildOrchestratorTools: ToolBuilder | undefined; let handleExperimentOrchestratorTool: ToolHandler | undefined; async function initializeDependencies() { try { console.log("πŸ”§ Loading MCP SDK..."); // Use the official MCP SDK const serverModule = await import("@modelcontextprotocol/sdk/server/index.js"); const stdioModule = await import("@modelcontextprotocol/sdk/server/stdio.js"); const typesModule = await import("@modelcontextprotocol/sdk/types.js"); console.log("πŸ”§ MCP SDK modules loaded, setting up exports..."); Server = serverModule.Server; StdioServerTransport = stdioModule.StdioServerTransport; CallToolRequestSchema = typesModule.CallToolRequestSchema; ListToolsRequestSchema = typesModule.ListToolsRequestSchema; InitializeRequestSchema = typesModule.InitializeRequestSchema; console.log("πŸ”§ MCP SDK setup complete"); console.log("πŸ”§ Server class:", typeof Server); } catch (error) { console.error("Failed to load MCP SDK:", error); throw error; } try { const casModule = await import("../../tools-cas/dist/index.js"); buildCASTools = casModule.buildCASTools; handleCASTool = casModule.handleCASTool; } catch (error) { console.warn("CAS tools not available:", error); } try { const plotModule = await import("../../tools-plot/dist/index.js"); buildPlotTools = plotModule.buildPlotTools; handlePlotTool = plotModule.handlePlotTool; } catch (error) { console.warn("Plot tools not available:", error); } try { const nliModule = await import("../../tools-nli/dist/index.js"); buildNLITools = nliModule.buildNLITools; handleNLITool = nliModule.handleNLITool; } catch (error) { console.warn("NLI tools not available:", error); } try { const unitsModule = await import("../../tools-units/dist/index.js"); buildUnitsTools = unitsModule.buildUnitsTools; handleUnitsTool = unitsModule.handleUnitsTool; } catch (error) { console.warn("Units tools not available:", error); } try { const constantsModule = await import("../../tools-constants/dist/index.js"); buildConstantsTools = constantsModule.buildConstantsTools; handleConstantsTool = constantsModule.handleConstantsTool; } catch (error) { console.warn("Constants tools not available:", error); } // Optional: reporting tools (schema only, local handler lives in server) try { const reportPath = "../../tools-report/dist/index.js"; const reportModule = await import(reportPath as any); buildReportTools = (reportModule as any).buildReportTools; } catch (error) { console.warn("Report tools not available:", error); } // Phase 3 tool packages (optional scaffolding) try { const tensorPath = "../../tools-tensor/dist/index.js"; const tensorModule = await import(tensorPath as any); buildTensorTools = (tensorModule as any).buildTensorTools; handleTensorTool = (tensorModule as any).handleTensorTool; } catch (error) { console.warn("Tensor tools not available:", error); } try { const quantumPath = "../../tools-quantum/dist/index.js"; const quantumModule = await import(quantumPath as any); buildQuantumTools = (quantumModule as any).buildQuantumTools; handleQuantumTool = (quantumModule as any).handleQuantumTool; } catch (error) { console.warn("Quantum tools not available:", error); } try { const statmechPath = "../../tools-statmech/dist/index.js"; const statmechModule = await import(statmechPath as any); buildStatmechTools = (statmechModule as any).buildStatmechTools; handleStatmechTool = (statmechModule as any).handleStatmechTool; } catch (error) { console.warn("StatMech tools not available:", error); } // Phase 4 tool packages try { const dataIOPath = "../../tools-data-io/dist/index.js"; const dataIOModule = await import(dataIOPath as any); buildDataIOTools = (dataIOModule as any).buildDataIOTools; handleDataIOTool = (dataIOModule as any).handleDataIOTool; } catch (error) { console.warn("Data I/O tools not available:", error); } // Signal tools are now consolidated under the 'data' tool // The tools-signal package is still used for handlers but not for tool registration try { const externalPath = "../../tools-external/dist/index.js"; const externalModule = await import(externalPath as any); buildExternalTools = (externalModule as any).buildExternalTools; handleExternalTool = (externalModule as any).handleExternalTool; } catch (error) { console.warn("External API tools not available:", error); } try { const exportPath = "../../tools-export/dist/index.js"; const exportModule = await import(exportPath as any); buildExportTools = (exportModule as any).buildExportTools; handleExportTool = (exportModule as any).handleExportTool; } catch (error) { console.warn("Export tools not available:", error); } // Phase 6 ML tools try { const mlPath = "../../tools-ml/dist/index.js"; const mlModule = await import(mlPath as any); buildMLTools = (mlModule as any).buildMLTools; handleMLAugmentationTool = (mlModule as any).handleMLAugmentationTool; } catch (error) { console.warn("ML tools not available:", error); } // Graphing Calculator tools try { const graphingCalcPath = "../../tools-graphing-calculator/dist/index.js"; const graphingCalcModule = await import(graphingCalcPath as any); buildGraphingCalculatorTools = (graphingCalcModule as any).buildGraphingCalculatorTools; handleGraphingCalculatorTool = (graphingCalcModule as any).handleGraphingCalculatorTool; } catch (error) { console.warn("Graphing Calculator tools not available:", error); } // Phase 7 & 8 tools try { const distributedPath = "../../tools-distributed/dist/index.js"; const distributedModule = await import(distributedPath as any); buildDistributedTools = (distributedModule as any).buildDistributedTools; handleDistributedCollaborationTool = (distributedModule as any).handleDistributedCollaborationTool; } catch (error) { console.warn("Distributed collaboration tools not available:", error); } try { const orchestratorPath = "../../tools-orchestrator/dist/index.js"; const orchestratorModule = await import(orchestratorPath as any); buildOrchestratorTools = (orchestratorModule as any).buildOrchestratorTools; handleExperimentOrchestratorTool = (orchestratorModule as any).handleExperimentOrchestratorTool; } catch (error) { console.warn("Experiment orchestrator tools not available:", error); } // Persistence manager try { const persistModule = await import("./persist.js"); getPersistenceManagerFn = (persistModule as any).getPersistenceManager; } catch (error) { console.warn("Persistence layer not available:", error); } } class PhysicsMCPServer { private server: any; private tools: MCPTool[] = []; constructor() { console.log("πŸ”§ Creating MCP Server instance..."); this.server = new Server({ name: "phys-mcp", version: "0.1.0", }, { capabilities: { tools: {} } }); console.log("πŸ”§ MCP Server instance created"); // Collect all available tools console.log("πŸ”§ Loading tools..."); try { // Core physics tools if (buildCASTools) { console.log("πŸ”§ Loading CAS tools..."); this.tools.push(...buildCASTools()); } if (buildUnitsTools) { console.log("πŸ”§ Loading Units tools..."); this.tools.push(...buildUnitsTools()); } if (buildConstantsTools) { console.log("πŸ”§ Loading Constants tools..."); this.tools.push(...buildConstantsTools()); } if (buildPlotTools) { console.log("πŸ”§ Loading Plot tools..."); this.tools.push(...buildPlotTools()); } if (buildNLITools) { console.log("πŸ”§ Loading NLI tools..."); this.tools.push(...buildNLITools()); } // Phase 3 tools (scaffolding) if (buildTensorTools) { console.log("πŸ”§ Loading Tensor tools..."); this.tools.push(...buildTensorTools()); } if (buildQuantumTools) { console.log("πŸ”§ Loading Quantum tools..."); this.tools.push(...buildQuantumTools()); } if (buildStatmechTools) { console.log("πŸ”§ Loading StatMech tools..."); this.tools.push(...buildStatmechTools()); } // Phase 4 tools if (buildDataIOTools) { console.log("πŸ”§ Loading Data I/O tools..."); this.tools.push(...buildDataIOTools()); } // Signal tools are now consolidated under the 'data' tool // Individual signal tools (data_fft, data_filter, etc.) are supported via legacy routing if (buildExternalTools) { console.log("πŸ”§ Loading External API tools..."); this.tools.push(...buildExternalTools()); } if (buildExportTools) { console.log("πŸ”§ Loading Export tools..."); this.tools.push(...buildExportTools()); } // Phase 6 ML tools if (buildMLTools) { console.log("πŸ”§ Loading ML tools..."); this.tools.push(...buildMLTools()); } // Graphing Calculator tools if (buildGraphingCalculatorTools) { console.log("πŸ”§ Loading Graphing Calculator tools..."); this.tools.push(...buildGraphingCalculatorTools()); } // Phase 7 & 8 tools if (buildDistributedTools) { console.log("πŸ”§ Loading Distributed Collaboration tools..."); this.tools.push(...buildDistributedTools()); } if (buildOrchestratorTools) { console.log("πŸ”§ Loading Experiment Orchestrator tools..."); this.tools.push(...buildOrchestratorTools()); } // Report tools (local handler) if (buildReportTools) { console.log("πŸ”§ Loading Report tools..."); this.tools.push(...buildReportTools()); } console.log("πŸ”§ Setting up handlers..."); this.setupHandlers(); console.log("πŸ”§ PhysicsMCPServer constructor complete"); } catch (error) { console.error("❌ Error in PhysicsMCPServer constructor:", error); throw error; } } private setupHandlers(): void { // Handle MCP initialization this.server.setRequestHandler(InitializeRequestSchema, async (request: MCPRequest) => { return { protocolVersion: "2024-11-05", capabilities: { tools: {}, }, serverInfo: { name: "phys-mcp", version: "0.1.0", }, }; }); // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: this.tools, }; }); // Handle tool calls this.server.setRequestHandler(CallToolRequestSchema, async (request: MCPRequest) => { const { name, arguments: args } = request.params; // Prepare persistence const pm = typeof getPersistenceManagerFn === 'function' ? getPersistenceManagerFn() : null; const sessionIdArg = (args && (args as any).session_id) ? (args as any).session_id : undefined; const sessionId = pm ? pm.ensureSession(sessionIdArg) : sessionIdArg; try { let result: Record<string, unknown>; // Local handler for report generation if (name === "report_generate") { if (!pm) { throw new Error("Persistence layer not available; cannot generate reports"); } result = await handleReportGenerate(args, pm, sessionId); } // Route to appropriate tool handler - consolidated tools else if (name === "cas" && handleCASTool) { result = await handleCASTool(name, args); } else if (name === "units_convert" && handleUnitsTool) { result = await handleUnitsTool(name, args); } else if (name === "constants_get" && handleConstantsTool) { result = await handleConstantsTool(name, args); } else if (name === "nli_parse" && handleNLITool) { result = await handleNLITool(name, args); } else if (name === "accel_caps") { // Handle acceleration capabilities directly result = { device: "cpu", capabilities: ["basic_compute"] }; } else if (name === "statmech_partition" && handleStatmechTool) { result = await handleStatmechTool(name, args); } else if (name === "plot" && handlePlotTool) { result = await handlePlotTool(name, args); } else if (name === "data" && handleDataIOTool) { result = await handleDataIOTool(name, args); } else if (name === "api_tools" && handleExternalTool) { result = await handleExternalTool(name, args); } else if (name === "export_tool" && handleExportTool) { result = await handleExportTool(name, args); } else if (name === "quantum" && handleQuantumTool) { result = await handleQuantumTool(name, args); } else if (name === "tensor_algebra" && handleTensorTool) { result = await handleTensorTool(name, args); } else if (name === "ml_ai_augmentation" && handleMLAugmentationTool) { result = await handleMLAugmentationTool(name, args); } else if (name === "graphing_calculator" && handleGraphingCalculatorTool) { result = await handleGraphingCalculatorTool(name, args); } else if (name === "distributed_collaboration" && handleDistributedCollaborationTool) { result = await handleDistributedCollaborationTool(name, args); } else if (name === "experiment_orchestrator" && handleExperimentOrchestratorTool) { result = await handleExperimentOrchestratorTool(name, args); } // Legacy support for individual tool names else if (name.startsWith("cas_") && handleCASTool) { result = await handleCASTool(name, args); } else if (name.startsWith("plot_") && handlePlotTool) { result = await handlePlotTool(name, args); } else if (name.startsWith("nli_") && handleNLITool) { result = await handleNLITool(name, args); } else if (name.startsWith("units_") && handleUnitsTool) { result = await handleUnitsTool(name, args); } else if (name.startsWith("constants_") && handleConstantsTool) { result = await handleConstantsTool(name, args); } else if (name.startsWith("tensor_") && handleTensorTool) { result = await handleTensorTool(name, args); } else if (name.startsWith("quantum_") && handleQuantumTool) { result = await handleQuantumTool(name, args); } else if (name.startsWith("statmech_") && handleStatmechTool) { result = await handleStatmechTool(name, args); } else if (name.startsWith("data_") && handleDataIOTool) { // Route all data tools to consolidated data handler result = await handleDataIOTool(name, args); } else if (name.startsWith("api_") && handleExternalTool) { result = await handleExternalTool(name, args); } else if (name.startsWith("export_") && handleExportTool) { result = await handleExportTool(name, args); } else if ((name === "symbolic_regression_train" || name === "surrogate_pde_train" || name === "pattern_recognition_infer" || name === "explain_derivation") && handleMLAugmentationTool) { result = await handleMLAugmentationTool(name, args); } else if (name.startsWith("calculator_") && handleGraphingCalculatorTool) { // Route legacy calculator tools to the main graphing_calculator tool const operation = name.replace("calculator_", ""); const updatedArgs = { ...args, operation }; result = await handleGraphingCalculatorTool("graphing_calculator", updatedArgs); } else if ((name === "job_submit" || name === "session_share" || name === "lab_notebook" || name === "artifact_versioning") && handleDistributedCollaborationTool) { result = await handleDistributedCollaborationTool(name, args); } else if ((name === "define_dag" || name === "validate_dag" || name === "run_dag" || name === "publish_report" || name === "collaborate_share") && handleExperimentOrchestratorTool) { result = await handleExperimentOrchestratorTool(name, args); } else { throw new Error(`Unknown tool: ${name}`); } // Save artifacts for common plot outputs let augmented = result; if (pm && sessionId) { augmented = await persistArtifactsIfAny(name, result, pm, sessionId); // Record event try { pm.recordEvent(sessionId, name, args, augmented); } catch (e) { console.warn("Failed to record event:", e); } } // Include session_id for client convenience const payload = (pm && sessionId) ? { ...augmented, session_id: sessionId } : augmented; return { content: [ { type: "text", text: JSON.stringify(payload, null, 2), }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(`Tool execution error for ${name}:`, error); return { content: [ { type: "text", text: `Error executing ${name}: ${errorMessage}`, }, ], isError: true, }; } }); } async start(): Promise<void> { const transport = new StdioServerTransport(); // Handle graceful shutdown process.on("SIGINT", () => this.shutdown()); process.on("SIGTERM", () => this.shutdown()); console.log("πŸš€ Physics MCP Server starting..."); console.log(`πŸ“Š Loaded ${this.tools.length} tools:`); for (const tool of this.tools) { console.log(` - ${tool.name}: ${tool.description}`); } console.log("🎯 Server ready for connections"); await this.server.connect(transport); } private shutdown(): void { console.error("πŸ›‘ Shutting down Physics MCP Server..."); // Cleanup Python worker if available if (handleCASTool && (handleCASTool as any).shutdownWorkerClient) { (handleCASTool as any).shutdownWorkerClient(); } // Clear singleton references serverInstance = null; process.env.PHYS_MCP_SERVER_RUNNING = 'false'; process.exit(0); } } // Singleton to prevent multiple server instances let serverInstance: PhysicsMCPServer | null = null; // Process-level singleton to prevent multiple instances across imports if (process.env.PHYS_MCP_SERVER_RUNNING === 'true') { console.error("⚠️ Phys-MCP server already running in this process"); process.exit(0); } process.env.PHYS_MCP_SERVER_RUNNING = 'true'; // Start the server async function main(): Promise<void> { // Prevent multiple instances if (serverInstance) { console.error("⚠️ Server instance already running"); return; } await initializeDependencies(); try { serverInstance = new PhysicsMCPServer(); await serverInstance.start(); } catch (error) { console.error("❌ Failed to start Physics MCP Server:", error); serverInstance = null; process.exit(1); } } // Only run if this is the main module const isMainModule = process.argv[1] && process.argv[1].endsWith('index.js'); if (isMainModule) { main().catch((error) => { console.error("❌ Unhandled error:", error); process.exit(1); }); } // --- Helpers --- async function handleReportGenerate(args: Record<string, unknown>, pm: PersistenceManager, sessionId?: string): Promise<Record<string, unknown>> { if (!pm) { throw new Error("Persistence layer not available; cannot generate reports"); } const { randomUUID } = await import('node:crypto'); const fs = await import('node:fs'); const sid = sessionId || (args && typeof args.session_id === 'string' ? args.session_id : undefined); if (!sid) { throw new Error("report_generate requires 'session_id'"); } const format = (args && typeof args.format === 'string' ? args.format : undefined) || 'markdown'; const title = (args && typeof args.title === 'string' ? args.title : undefined) || 'Physics MCP Session Report'; const author = (args && typeof args.author === 'string' ? args.author : undefined) || ''; const include = (args && args.include) || ["cas", "plots", "constants", "units"]; // advisory only for now // Gather session data const events = pm.getSessionEvents(sid) || []; const artifacts = pm.getSessionArtifacts(sid) || []; const now = new Date().toISOString(); // Build Markdown report let md = `# ${title}\n\n`; if (author) md += `Author: ${author}\n\n`; md += `Generated: ${now}\n\n`; md += `Session ID: ${sid}\n\n`; md += `## Summary\n`; md += `- Total events: ${events.length}\n`; md += `- Total artifacts: ${artifacts.length}\n\n`; md += `## Events\n`; for (const ev of events) { md += `- Tool: ${ev.tool_name} at ${new Date(ev.ts).toISOString()}\n`; try { const inputObj = JSON.parse(ev.input_json); md += " - Input:\n\n"; md += "```json\n" + JSON.stringify(inputObj, null, 2) + "\n```\n"; } catch { // Ignore JSON parsing errors for input } try { const outputObj = JSON.parse(ev.output_json); md += " - Output:\n\n"; md += "```json\n" + JSON.stringify(outputObj, null, 2) + "\n```\n"; } catch { // Ignore JSON parsing errors for output } } md += `\n## Artifacts\n`; for (const a of artifacts) { md += `- ${a.kind}: ${a.path}\n`; } const filename = `report-${randomUUID()}.md`; const reportPath = pm.getArtifactPath(sid, filename); fs.writeFileSync(reportPath, md, { encoding: 'utf-8' }); // Record artifact in DB pm.recordArtifact(sid, 'report_markdown', reportPath, { title, author, format, include }); const stats = fs.statSync(reportPath); return { report_path: reportPath, format: 'markdown', bytes: stats.size, session_id: sid, title: title, author: author }; } async function persistArtifactsIfAny(name: string, result: Record<string, unknown>, pm: PersistenceManager, sessionId: string): Promise<Record<string, unknown>> { if (!result || typeof result !== 'object') return result; const fs = await import('node:fs'); const { randomUUID } = await import('node:crypto'); const artifactsMeta: Array<{kind: string; path: string}> = []; // Save PNG image if present if (result.image_png_b64 && typeof result.image_png_b64 === 'string') { try { const imgBuffer = Buffer.from(result.image_png_b64, 'base64'); const pngName = `${name}-${randomUUID()}.png`; const pngPath = pm.getArtifactPath(sessionId, pngName); fs.writeFileSync(pngPath, imgBuffer); pm.recordArtifact(sessionId, 'plot_image', pngPath, { tool: name }); artifactsMeta.push({ kind: 'plot_image', path: pngPath }); // Remove inline base64 to reduce payload; keep a small preview? For now, keep it and also attach path. } catch (e) { console.warn('Failed to persist PNG artifact:', e); } } // Save CSV if present if (result.csv_data && typeof result.csv_data === 'string') { try { const csvName = `${name}-${randomUUID()}.csv`; const csvPath = pm.getArtifactPath(sessionId, csvName); fs.writeFileSync(csvPath, result.csv_data, { encoding: 'utf-8' }); pm.recordArtifact(sessionId, 'plot_csv', csvPath, { tool: name }); artifactsMeta.push({ kind: 'plot_csv', path: csvPath }); } catch (e) { console.warn('Failed to persist CSV artifact:', e); } } // Save SVG if present if (result.image_svg && typeof result.image_svg === 'string') { try { const svgName = `${name}-${randomUUID()}.svg`; const svgPath = pm.getArtifactPath(sessionId, svgName); fs.writeFileSync(svgPath, result.image_svg, { encoding: 'utf-8' }); pm.recordArtifact(sessionId, 'plot_svg', svgPath, { tool: name }); artifactsMeta.push({ kind: 'plot_svg', path: svgPath }); } catch (e) { console.warn('Failed to persist SVG artifact:', e); } } if (artifactsMeta.length) { return { ...result, artifacts: artifactsMeta }; } return result; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/BlinkZer0/Phys-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server