Spaces:
Running
Running
owenkaplinsky
commited on
Commit
·
a0c3289
1
Parent(s):
67dfe8d
Add MCP block in/out tool
Browse files- project/chat.py +126 -0
- project/src/index.js +86 -1
- project/unified_server.py +4 -0
project/chat.py
CHANGED
|
@@ -39,6 +39,10 @@ creation_results = {}
|
|
| 39 |
variable_queue = queue.Queue()
|
| 40 |
variable_results = {}
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
# Global variable to store the deployed HF MCP server URL
|
| 43 |
current_mcp_server_url = None
|
| 44 |
|
|
@@ -223,6 +227,53 @@ def create_variable(var_name):
|
|
| 223 |
traceback.print_exc()
|
| 224 |
return f"Error creating variable: {str(e)}"
|
| 225 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
# Unified Server-Sent Events endpoint for all workspace operations
|
| 227 |
@app.get("/unified_stream")
|
| 228 |
async def unified_stream():
|
|
@@ -294,6 +345,24 @@ async def unified_stream():
|
|
| 294 |
else:
|
| 295 |
print(f"[SSE SKIP] Skipping duplicate request for ID: {request_id}")
|
| 296 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
else:
|
| 298 |
# Send a heartbeat every 30 seconds to keep connection alive
|
| 299 |
heartbeat_counter += 1
|
|
@@ -374,6 +443,24 @@ async def variable_result(request: Request):
|
|
| 374 |
|
| 375 |
return {"received": True}
|
| 376 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 377 |
def deploy_to_huggingface(space_name):
|
| 378 |
"""Deploy the generated MCP code to a Hugging Face Space"""
|
| 379 |
global stored_hf_key
|
|
@@ -665,6 +752,38 @@ Note: Users can see tool response outputs verbatim. You don't have to repeat the
|
|
| 665 |
"required": ["name"],
|
| 666 |
}
|
| 667 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 668 |
{
|
| 669 |
"type": "function",
|
| 670 |
"name": "deploy_to_huggingface",
|
|
@@ -885,6 +1004,13 @@ Note: Users can see tool response outputs verbatim. You don't have to repeat the
|
|
| 885 |
tool_result = create_variable(name)
|
| 886 |
result_label = "Create Var Operation"
|
| 887 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 888 |
elif function_name == "deploy_to_huggingface":
|
| 889 |
space_name = function_args.get("space_name", "")
|
| 890 |
print(Fore.YELLOW + f"Agent deploying to Hugging Face Space `{space_name}`." + Style.RESET_ALL)
|
|
|
|
| 39 |
variable_queue = queue.Queue()
|
| 40 |
variable_results = {}
|
| 41 |
|
| 42 |
+
# Queue for edit MCP requests and results storage
|
| 43 |
+
edit_mcp_queue = queue.Queue()
|
| 44 |
+
edit_mcp_results = {}
|
| 45 |
+
|
| 46 |
# Global variable to store the deployed HF MCP server URL
|
| 47 |
current_mcp_server_url = None
|
| 48 |
|
|
|
|
| 227 |
traceback.print_exc()
|
| 228 |
return f"Error creating variable: {str(e)}"
|
| 229 |
|
| 230 |
+
def edit_mcp(inputs=None, outputs=None):
|
| 231 |
+
"""Edit the inputs and outputs of the create_mcp block"""
|
| 232 |
+
try:
|
| 233 |
+
print(f"[EDIT MCP REQUEST] Attempting to edit MCP block: inputs={inputs}, outputs={outputs}")
|
| 234 |
+
|
| 235 |
+
# Generate a unique request ID
|
| 236 |
+
request_id = str(uuid.uuid4())
|
| 237 |
+
|
| 238 |
+
# Clear any old results for this request ID first
|
| 239 |
+
if request_id in edit_mcp_results:
|
| 240 |
+
edit_mcp_results.pop(request_id)
|
| 241 |
+
|
| 242 |
+
# Build the edit data
|
| 243 |
+
edit_data = {"request_id": request_id}
|
| 244 |
+
if inputs is not None:
|
| 245 |
+
edit_data["inputs"] = inputs
|
| 246 |
+
if outputs is not None:
|
| 247 |
+
edit_data["outputs"] = outputs
|
| 248 |
+
|
| 249 |
+
# Add to edit MCP queue
|
| 250 |
+
edit_mcp_queue.put(edit_data)
|
| 251 |
+
print(f"[EDIT MCP REQUEST] Added to queue with ID: {request_id}")
|
| 252 |
+
|
| 253 |
+
# Wait for result with timeout
|
| 254 |
+
timeout = 8 # 8 seconds timeout
|
| 255 |
+
start_time = time.time()
|
| 256 |
+
check_interval = 0.05 # Check more frequently
|
| 257 |
+
|
| 258 |
+
while time.time() - start_time < timeout:
|
| 259 |
+
if request_id in edit_mcp_results:
|
| 260 |
+
result = edit_mcp_results.pop(request_id)
|
| 261 |
+
print(f"[EDIT MCP RESULT] Received result for {request_id}: success={result.get('success')}, error={result.get('error')}")
|
| 262 |
+
if result["success"]:
|
| 263 |
+
return f"[TOOL] Successfully edited MCP block inputs/outputs"
|
| 264 |
+
else:
|
| 265 |
+
return f"[TOOL] Failed to edit MCP block: {result.get('error', 'Unknown error')}"
|
| 266 |
+
time.sleep(check_interval)
|
| 267 |
+
|
| 268 |
+
print(f"[EDIT MCP TIMEOUT] No response received for request {request_id} after {timeout} seconds")
|
| 269 |
+
return f"Timeout waiting for MCP edit confirmation"
|
| 270 |
+
|
| 271 |
+
except Exception as e:
|
| 272 |
+
print(f"[EDIT MCP ERROR] {e}")
|
| 273 |
+
import traceback
|
| 274 |
+
traceback.print_exc()
|
| 275 |
+
return f"Error editing MCP block: {str(e)}"
|
| 276 |
+
|
| 277 |
# Unified Server-Sent Events endpoint for all workspace operations
|
| 278 |
@app.get("/unified_stream")
|
| 279 |
async def unified_stream():
|
|
|
|
| 345 |
else:
|
| 346 |
print(f"[SSE SKIP] Skipping duplicate request for ID: {request_id}")
|
| 347 |
|
| 348 |
+
# Check edit MCP queue
|
| 349 |
+
elif not edit_mcp_queue.empty():
|
| 350 |
+
edit_request = edit_mcp_queue.get_nowait()
|
| 351 |
+
request_id = edit_request.get("request_id")
|
| 352 |
+
request_key = f"edit_mcp_{request_id}"
|
| 353 |
+
|
| 354 |
+
# Avoid sending duplicate requests too quickly
|
| 355 |
+
if request_key not in sent_requests:
|
| 356 |
+
sent_requests.add(request_key)
|
| 357 |
+
edit_request["type"] = "edit_mcp" # Add type identifier
|
| 358 |
+
print(f"[SSE SEND] Sending edit MCP request with ID: {request_id}")
|
| 359 |
+
yield f"data: {json.dumps(edit_request)}\n\n"
|
| 360 |
+
|
| 361 |
+
# Clear from sent_requests after 10 seconds
|
| 362 |
+
asyncio.create_task(clear_sent_request(sent_requests, request_key, 10))
|
| 363 |
+
else:
|
| 364 |
+
print(f"[SSE SKIP] Skipping duplicate request for ID: {request_id}")
|
| 365 |
+
|
| 366 |
else:
|
| 367 |
# Send a heartbeat every 30 seconds to keep connection alive
|
| 368 |
heartbeat_counter += 1
|
|
|
|
| 443 |
|
| 444 |
return {"received": True}
|
| 445 |
|
| 446 |
+
# Endpoint to receive edit MCP results from frontend
|
| 447 |
+
@app.post("/edit_mcp_result")
|
| 448 |
+
async def edit_mcp_result(request: Request):
|
| 449 |
+
"""Receive edit MCP results from the frontend"""
|
| 450 |
+
data = await request.json()
|
| 451 |
+
request_id = data.get("request_id")
|
| 452 |
+
success = data.get("success")
|
| 453 |
+
error = data.get("error")
|
| 454 |
+
|
| 455 |
+
print(f"[EDIT MCP RESULT RECEIVED] request_id={request_id}, success={success}, error={error}")
|
| 456 |
+
|
| 457 |
+
if request_id:
|
| 458 |
+
# Store the result for the edit_mcp function to retrieve
|
| 459 |
+
edit_mcp_results[request_id] = data
|
| 460 |
+
print(f"[EDIT MCP RESULT STORED] Results dict now has {len(edit_mcp_results)} items")
|
| 461 |
+
|
| 462 |
+
return {"received": True}
|
| 463 |
+
|
| 464 |
def deploy_to_huggingface(space_name):
|
| 465 |
"""Deploy the generated MCP code to a Hugging Face Space"""
|
| 466 |
global stored_hf_key
|
|
|
|
| 752 |
"required": ["name"],
|
| 753 |
}
|
| 754 |
},
|
| 755 |
+
{
|
| 756 |
+
"type": "function",
|
| 757 |
+
"name": "edit_mcp",
|
| 758 |
+
"description": "Edit the inputs and outputs of the create_mcp block.",
|
| 759 |
+
"parameters": {
|
| 760 |
+
"type": "object",
|
| 761 |
+
"properties": {
|
| 762 |
+
"inputs": {
|
| 763 |
+
"type": "array",
|
| 764 |
+
"items": {
|
| 765 |
+
"type": "object",
|
| 766 |
+
"properties": {
|
| 767 |
+
"name": { "type": "string" },
|
| 768 |
+
"type": { "type": "string", "enum": ["string", "integer", "list"] }
|
| 769 |
+
},
|
| 770 |
+
"required": ["name", "type"]
|
| 771 |
+
}
|
| 772 |
+
},
|
| 773 |
+
"outputs": {
|
| 774 |
+
"type": "array",
|
| 775 |
+
"items": {
|
| 776 |
+
"type": "object",
|
| 777 |
+
"properties": {
|
| 778 |
+
"name": { "type": "string" },
|
| 779 |
+
"type": { "type": "string", "enum": ["string", "integer", "list"] }
|
| 780 |
+
},
|
| 781 |
+
"required": ["name", "type"]
|
| 782 |
+
}
|
| 783 |
+
}
|
| 784 |
+
}
|
| 785 |
+
}
|
| 786 |
+
},
|
| 787 |
{
|
| 788 |
"type": "function",
|
| 789 |
"name": "deploy_to_huggingface",
|
|
|
|
| 1004 |
tool_result = create_variable(name)
|
| 1005 |
result_label = "Create Var Operation"
|
| 1006 |
|
| 1007 |
+
elif function_name == "edit_mcp":
|
| 1008 |
+
inputs = function_args.get("inputs", None)
|
| 1009 |
+
outputs = function_args.get("outputs", None)
|
| 1010 |
+
print(Fore.YELLOW + f"Agent editing MCP block: inputs={inputs}, outputs={outputs}." + Style.RESET_ALL)
|
| 1011 |
+
tool_result = edit_mcp(inputs, outputs)
|
| 1012 |
+
result_label = "Edit MCP Operation"
|
| 1013 |
+
|
| 1014 |
elif function_name == "deploy_to_huggingface":
|
| 1015 |
space_name = function_args.get("space_name", "")
|
| 1016 |
print(Fore.YELLOW + f"Agent deploying to Hugging Face Space `{space_name}`." + Style.RESET_ALL)
|
project/src/index.js
CHANGED
|
@@ -243,6 +243,8 @@ const setupUnifiedStream = () => {
|
|
| 243 |
requestKey = `create_${data.request_id}`;
|
| 244 |
} else if (data.type === 'variable') {
|
| 245 |
requestKey = `variable_${data.request_id}`;
|
|
|
|
|
|
|
| 246 |
}
|
| 247 |
|
| 248 |
// Skip if we've already processed this request
|
|
@@ -256,8 +258,91 @@ const setupUnifiedStream = () => {
|
|
| 256 |
setTimeout(() => processedRequests.delete(requestKey), 10000);
|
| 257 |
}
|
| 258 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
// Handle deletion requests
|
| 260 |
-
if (data.type === 'delete' && data.block_id) {
|
| 261 |
console.log('[SSE] Received deletion request for block:', data.block_id);
|
| 262 |
|
| 263 |
// Try to delete the block
|
|
|
|
| 243 |
requestKey = `create_${data.request_id}`;
|
| 244 |
} else if (data.type === 'variable') {
|
| 245 |
requestKey = `variable_${data.request_id}`;
|
| 246 |
+
} else if (data.type === 'edit_mcp') {
|
| 247 |
+
requestKey = `edit_mcp_${data.request_id}`;
|
| 248 |
}
|
| 249 |
|
| 250 |
// Skip if we've already processed this request
|
|
|
|
| 258 |
setTimeout(() => processedRequests.delete(requestKey), 10000);
|
| 259 |
}
|
| 260 |
|
| 261 |
+
// Handle edit MCP requests
|
| 262 |
+
if (data.type === 'edit_mcp' && data.request_id) {
|
| 263 |
+
console.log('[SSE] Received edit MCP request:', data);
|
| 264 |
+
|
| 265 |
+
let success = false;
|
| 266 |
+
let error = null;
|
| 267 |
+
|
| 268 |
+
try {
|
| 269 |
+
// Find the create_mcp block
|
| 270 |
+
const mcpBlocks = ws.getBlocksByType('create_mcp');
|
| 271 |
+
const mcpBlock = mcpBlocks[0];
|
| 272 |
+
|
| 273 |
+
if (!mcpBlock) {
|
| 274 |
+
throw new Error('No create_mcp block found in workspace');
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
// Disable events to prevent infinite loops
|
| 278 |
+
Blockly.Events.disable();
|
| 279 |
+
|
| 280 |
+
try {
|
| 281 |
+
// Create a container block for the mutator
|
| 282 |
+
const containerBlock = ws.newBlock('container');
|
| 283 |
+
containerBlock.initSvg();
|
| 284 |
+
|
| 285 |
+
// Build inputs if provided
|
| 286 |
+
if (data.inputs && Array.isArray(data.inputs)) {
|
| 287 |
+
let connection = containerBlock.getInput('STACK').connection;
|
| 288 |
+
for (let idx = 0; idx < data.inputs.length; idx++) {
|
| 289 |
+
const input = data.inputs[idx];
|
| 290 |
+
const itemBlock = ws.newBlock('container_input');
|
| 291 |
+
itemBlock.initSvg();
|
| 292 |
+
itemBlock.setFieldValue(input.type || 'string', 'TYPE');
|
| 293 |
+
itemBlock.setFieldValue(input.name || '', 'NAME');
|
| 294 |
+
connection.connect(itemBlock.previousConnection);
|
| 295 |
+
connection = itemBlock.nextConnection;
|
| 296 |
+
}
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
// Build outputs if provided
|
| 300 |
+
if (data.outputs && Array.isArray(data.outputs)) {
|
| 301 |
+
let connection2 = containerBlock.getInput('STACK2').connection;
|
| 302 |
+
for (let idx = 0; idx < data.outputs.length; idx++) {
|
| 303 |
+
const output = data.outputs[idx];
|
| 304 |
+
const itemBlock = ws.newBlock('container_output');
|
| 305 |
+
itemBlock.initSvg();
|
| 306 |
+
itemBlock.setFieldValue(output.type || 'string', 'TYPE');
|
| 307 |
+
itemBlock.setFieldValue(output.name || 'output', 'NAME');
|
| 308 |
+
connection2.connect(itemBlock.previousConnection);
|
| 309 |
+
connection2 = itemBlock.nextConnection;
|
| 310 |
+
}
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
// Apply changes using the compose method
|
| 314 |
+
mcpBlock.compose(containerBlock);
|
| 315 |
+
|
| 316 |
+
// Clean up
|
| 317 |
+
containerBlock.dispose();
|
| 318 |
+
success = true;
|
| 319 |
+
console.log('[SSE] Successfully edited MCP block');
|
| 320 |
+
} finally {
|
| 321 |
+
Blockly.Events.enable();
|
| 322 |
+
}
|
| 323 |
+
} catch (e) {
|
| 324 |
+
error = e.toString();
|
| 325 |
+
console.error('[SSE] Error editing MCP block:', e);
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
// Send result back to backend immediately
|
| 329 |
+
console.log('[SSE] Sending edit MCP result:', { request_id: data.request_id, success, error });
|
| 330 |
+
fetch('/edit_mcp_result', {
|
| 331 |
+
method: 'POST',
|
| 332 |
+
headers: { 'Content-Type': 'application/json' },
|
| 333 |
+
body: JSON.stringify({
|
| 334 |
+
request_id: data.request_id,
|
| 335 |
+
success: success,
|
| 336 |
+
error: error
|
| 337 |
+
})
|
| 338 |
+
}).then(response => {
|
| 339 |
+
console.log('[SSE] Edit MCP result sent successfully');
|
| 340 |
+
}).catch(err => {
|
| 341 |
+
console.error('[SSE] Error sending edit MCP result:', err);
|
| 342 |
+
});
|
| 343 |
+
}
|
| 344 |
// Handle deletion requests
|
| 345 |
+
else if (data.type === 'delete' && data.block_id) {
|
| 346 |
console.log('[SSE] Received deletion request for block:', data.block_id);
|
| 347 |
|
| 348 |
// Try to delete the block
|
project/unified_server.py
CHANGED
|
@@ -50,6 +50,10 @@ async def deletion_result_route(request: Request):
|
|
| 50 |
async def variable_result_route(request: Request):
|
| 51 |
return await chat.variable_result(request)
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
# === test.py API endpoints ===
|
| 55 |
@app.post("/update_code")
|
|
|
|
| 50 |
async def variable_result_route(request: Request):
|
| 51 |
return await chat.variable_result(request)
|
| 52 |
|
| 53 |
+
@app.post("/edit_mcp_result")
|
| 54 |
+
async def edit_mcp_result_route(request: Request):
|
| 55 |
+
return await chat.edit_mcp_result(request)
|
| 56 |
+
|
| 57 |
|
| 58 |
# === test.py API endpoints ===
|
| 59 |
@app.post("/update_code")
|