Jake Shore 96e52666c5 MCPEngine full sync — studio scaffold, factory v2, server updates, state.json — 2026-02-12
=== NEW ===
- studio/ — MCPEngine Studio scaffold (Next.js monorepo, build plan)
- docs/FACTORY-V2.md — Factory v2 architecture doc
- docs/CALENDLY_MCP_BUILD_SUMMARY.md — Calendly MCP build report

=== UPDATED SERVERS ===
- fieldedge: Added jobs-tools, UI build script, main entry update
- lightspeed: Updated main + server entry points
- squarespace: Added collection-browser + page-manager apps
- toast: Added main + server entry points

=== INFRA ===
- infra/command-center/state.json — Updated pipeline state
- infra/command-center/FACTORY-V2.md — Factory v2 operator playbook
2026-02-12 17:58:33 -05:00

208 lines
6.6 KiB
TypeScript

'use client';
import React, { useCallback, useEffect, useState } from 'react';
import { ReactFlowProvider } from '@xyflow/react';
import {
Sparkles,
FlaskConical,
Rocket,
Plus,
} from 'lucide-react';
import { ToolCanvas } from '../../../../components/canvas/ToolCanvas';
import { ToolInspector } from '../../../../components/inspector/ToolInspector';
import { useCanvasState } from '../../../../hooks/useCanvasState';
import type { ToolDefinition } from '@mcpengine/ai-pipeline/types';
import type { ToolNodeData } from '../../../../components/canvas/ToolNode';
// Mock data for development — replaced by API calls in production
const MOCK_TOOLS: ToolDefinition[] = [
{
name: 'list_contacts',
description: 'Retrieve a paginated list of contacts with optional filtering by tags, dates, and custom fields.',
method: 'GET',
endpoint: '/contacts',
inputSchema: {
type: 'object',
properties: {
limit: { type: 'number', description: 'Max results to return' },
offset: { type: 'number', description: 'Pagination offset' },
tag: { type: 'string', description: 'Filter by tag' },
},
required: [],
},
annotations: { readOnlyHint: true, idempotentHint: true },
},
{
name: 'create_contact',
description: 'Create a new contact with the provided information.',
method: 'POST',
endpoint: '/contacts',
inputSchema: {
type: 'object',
properties: {
email: { type: 'string', description: 'Contact email address' },
name: { type: 'string', description: 'Full name' },
phone: { type: 'string', description: 'Phone number' },
},
required: ['email', 'name'],
},
},
{
name: 'delete_contact',
description: 'Permanently delete a contact by their ID. This action cannot be undone.',
method: 'DELETE',
endpoint: '/contacts/{id}',
inputSchema: {
type: 'object',
properties: {
id: { type: 'string', description: 'Contact ID' },
},
required: ['id'],
},
annotations: { destructiveHint: true },
},
{
name: 'update_contact',
description: 'Update an existing contact\'s information.',
method: 'PUT',
endpoint: '/contacts/{id}',
inputSchema: {
type: 'object',
properties: {
id: { type: 'string', description: 'Contact ID' },
email: { type: 'string', description: 'Updated email' },
name: { type: 'string', description: 'Updated name' },
},
required: ['id'],
},
annotations: { idempotentHint: true },
},
];
export default function ProjectEditorPage() {
const [tools, setTools] = useState<ToolDefinition[]>(MOCK_TOOLS);
const {
selectedNodeId,
inspectorOpen,
nodes,
initializeFromTools,
selectNode,
updateTool,
removeTool,
} = useCanvasState();
// Initialize canvas from tools
useEffect(() => {
initializeFromTools(tools);
}, []); // eslint-disable-line react-hooks/exhaustive-deps
// Get selected tool
const selectedTool = selectedNodeId
? (nodes.find((n) => n.id === selectedNodeId)?.data as ToolNodeData | undefined)?.tool
: null;
const handleToolSelect = useCallback(
(toolName: string | null) => {
selectNode(toolName);
},
[selectNode]
);
const handleToolsChange = useCallback((updated: ToolDefinition[]) => {
setTools(updated);
}, []);
const handleToolChange = useCallback(
(updatedTool: ToolDefinition) => {
if (!selectedNodeId) return;
updateTool(selectedNodeId, updatedTool);
setTools((prev) =>
prev.map((t) => (t.name === selectedNodeId ? updatedTool : t))
);
},
[selectedNodeId, updateTool]
);
const handleToolDelete = useCallback(
(toolName: string) => {
removeTool(toolName);
setTools((prev) => prev.filter((t) => t.name !== toolName));
},
[removeTool]
);
const handleCloseInspector = useCallback(() => {
selectNode(null);
}, [selectNode]);
return (
<div className="flex flex-col h-screen bg-[#0A0F1E]">
{/* Top Bar */}
<header className="flex items-center justify-between px-6 py-3 border-b border-gray-800 bg-gray-900/80 backdrop-blur-sm z-10">
<div className="flex items-center gap-3">
<div className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse" />
<h1 className="text-sm font-semibold text-gray-100">
Project Editor
</h1>
<span className="text-xs text-gray-500">
{tools.length} tools
</span>
</div>
<div className="flex items-center gap-2">
<button className="flex items-center gap-1.5 px-3 py-1.5 bg-gray-800 border border-gray-700 rounded-lg text-xs text-gray-300 hover:border-gray-600 transition-colors">
<Plus className="w-3.5 h-3.5" />
Add Tool
</button>
<div className="w-px h-6 bg-gray-800" />
<button className="flex items-center gap-1.5 px-3 py-1.5 bg-gray-800 border border-gray-700 rounded-lg text-xs text-gray-300 hover:border-gray-600 transition-colors">
<Sparkles className="w-3.5 h-3.5 text-amber-400" />
Analyze
</button>
<button className="flex items-center gap-1.5 px-3 py-1.5 bg-gray-800 border border-gray-700 rounded-lg text-xs text-gray-300 hover:border-gray-600 transition-colors">
<FlaskConical className="w-3.5 h-3.5 text-blue-400" />
Test
</button>
<button className="flex items-center gap-1.5 px-3 py-1.5 bg-indigo-600 hover:bg-indigo-500 rounded-lg text-xs text-white transition-colors">
<Rocket className="w-3.5 h-3.5" />
Deploy
</button>
</div>
</header>
{/* Main Content */}
<div className="flex flex-1 overflow-hidden">
{/* Canvas */}
<ReactFlowProvider>
<div className="flex-1 relative">
<ToolCanvas
tools={tools}
onToolSelect={handleToolSelect}
onToolsChange={handleToolsChange}
/>
</div>
</ReactFlowProvider>
{/* Inspector Panel — slides in from right */}
<div
className={`
transition-all duration-300 ease-in-out overflow-hidden
${inspectorOpen && selectedTool ? 'w-[380px]' : 'w-0'}
`}
>
{selectedTool && (
<ToolInspector
tool={selectedTool}
onChange={handleToolChange}
onDelete={handleToolDelete}
onClose={handleCloseInspector}
/>
)}
</div>
</div>
</div>
);
}