guohanghui commited on
Commit
ab2cddc
·
verified ·
1 Parent(s): b98b2f4

Update biopython/mcp_output/mcp_plugin/mcp_service.py

Browse files
biopython/mcp_output/mcp_plugin/mcp_service.py CHANGED
@@ -356,64 +356,28 @@ def blast_get_result(payload: dict):
356
  # BLAST 搜索工具 - BLAST Search Tools (同步版本,保留用于小序列)
357
  # ==============================================
358
 
359
- @mcp.tool(name="blast_search", description="Search NCBI database using BLAST to find similar sequences")
360
- def blast_search(payload: dict):
361
- """
362
- 使用 BLAST 搜索相似序列(稳定版)
363
- Required fields: sequence
364
- Optional fields:
365
- - database (nt/nr/refseq_rna, default refseq_rna)
366
- - program (blastn/blastp, default blastn)
367
- - hitlist_size (default 20)
368
- - expect (default 1e-10)
369
- """
370
- try:
371
- sequence = str(payload.get("sequence", "")).strip()
372
- if not sequence:
373
- return {"success": False, "result": None, "error": "sequence is required"}
374
-
375
- # 清理序列
376
- sequence = "".join(sequence.split()).upper()
377
 
378
- # 参数
379
- database = payload.get("database", "refseq_rna")
380
- program = payload.get("program", "blastn")
381
- hitlist_size = int(payload.get("hitlist_size", 20))
382
- hitlist_size = 5
383
- expect = float(payload.get("expect", 1e-10))
384
-
385
- # 提示信息
386
- if database == "nt":
387
- print("⚠️ Warning: 'nt' database is very large and may take 5-15 minutes!")
388
- print("💡 Consider using 'refseq_rna' for faster results")
389
-
390
- print(f"🔍 Submitting BLAST search: {len(sequence)} bp, database={database}, hits={hitlist_size}")
391
- print("⏳ Please wait...")
392
-
393
- # 提交 BLAST
394
- try:
395
- handle = NCBIWWW.qblast(
396
- program=program,
397
- database=database,
398
- sequence=sequence,
399
- hitlist_size=hitlist_size,
400
- expect=expect,
401
- format_type="XML"
402
- )
403
- except Exception as blast_error:
404
- return {
405
- "success": False,
406
- "result": None,
407
- "error": f"BLAST submission failed: {str(blast_error)}"
408
- }
409
 
410
- # 迭代解析 XML
411
  hits = []
412
  for blast_record in NCBIXML.parse(handle):
413
  for idx, alignment in enumerate(blast_record.alignments[:hitlist_size]):
414
  if not alignment.hsps:
415
  continue
416
-
417
  hsp = alignment.hsps[0]
418
  align_len = int(hsp.align_length)
419
  identities = int(hsp.identities)
@@ -432,25 +396,78 @@ def blast_search(payload: dict):
432
  "align_length": int(align_len)
433
  })
434
 
435
- # 输出进度,保持 SSE 活跃
436
- print(f"Processing alignment {idx+1}/{min(hitlist_size, len(blast_record.alignments))}")
437
-
438
  handle.close()
439
- print(f"✅ BLAST search completed: {len(hits)} alignments found")
440
 
441
- # 构建返回结果
442
- result = {
443
- "query_length": int(len(sequence)),
444
- "database": str(database),
445
- "program": str(program),
446
- "total_hits": int(len(hits)),
447
- "hits": hits
 
 
 
448
  }
449
 
450
- return {"success": True, "result": result, "error": None}
451
-
452
  except Exception as e:
453
- return {"success": False, "result": None, "error": str(e)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
454
 
455
 
456
  @mcp.tool(name="filter_blast_hits", description="Filter BLAST hits by identity percentage threshold")
 
356
  # BLAST 搜索工具 - BLAST Search Tools (同步版本,保留用于小序列)
357
  # ==============================================
358
 
359
+ # 全局存储任务结果
360
+ BLAST_TASKS = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
 
362
+ # --------------------------
363
+ # 异步 BLAST 执行函数
364
+ # --------------------------
365
+ def run_blast_task(task_id, sequence, database, program, hitlist_size, expect):
366
+ try:
367
+ handle = NCBIWWW.qblast(
368
+ program=program,
369
+ database=database,
370
+ sequence=sequence,
371
+ hitlist_size=hitlist_size,
372
+ expect=expect,
373
+ format_type="XML"
374
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
 
 
376
  hits = []
377
  for blast_record in NCBIXML.parse(handle):
378
  for idx, alignment in enumerate(blast_record.alignments[:hitlist_size]):
379
  if not alignment.hsps:
380
  continue
 
381
  hsp = alignment.hsps[0]
382
  align_len = int(hsp.align_length)
383
  identities = int(hsp.identities)
 
396
  "align_length": int(align_len)
397
  })
398
 
 
 
 
399
  handle.close()
 
400
 
401
+ BLAST_TASKS[task_id] = {
402
+ "status": "completed",
403
+ "result": {
404
+ "query_length": len(sequence),
405
+ "database": database,
406
+ "program": program,
407
+ "total_hits": len(hits),
408
+ "hits": hits
409
+ },
410
+ "error": None
411
  }
412
 
 
 
413
  except Exception as e:
414
+ BLAST_TASKS[task_id] = {
415
+ "status": "failed",
416
+ "result": None,
417
+ "error": str(e)
418
+ }
419
+
420
+ # --------------------------
421
+ # 单工具:blast_search
422
+ # --------------------------
423
+ @mcp.tool(name="blast_search", description="Asynchronous BLAST search: returns task_id immediately")
424
+ def blast_search(payload: dict):
425
+ """
426
+ Single MCP tool for asynchronous BLAST search.
427
+ Required: sequence
428
+ Optional: database (refseq_rna/nt/nr), program (blastn/blastp), hitlist_size, expect
429
+ """
430
+
431
+ sequence = payload.get("sequence", "")
432
+ if not sequence:
433
+ return {"success": False, "result": None, "error": "sequence is required"}
434
+
435
+ database = payload.get("database", "refseq_rna")
436
+ program = payload.get("program", "blastn")
437
+ hitlist_size = int(payload.get("hitlist_size", 5))
438
+ expect = float(payload.get("expect", 1e-10))
439
+
440
+ task_id = str(uuid.uuid4())
441
+ BLAST_TASKS[task_id] = {"status": "running", "result": None, "error": None}
442
+
443
+ # 后��线程执行 BLAST
444
+ thread = threading.Thread(
445
+ target=run_blast_task,
446
+ args=(task_id, sequence, database, program, hitlist_size, expect)
447
+ )
448
+ thread.start()
449
+
450
+ # 立即返回 task_id
451
+ return {
452
+ "success": True,
453
+ "result": {"task_id": task_id, "status": "running"},
454
+ "error": None
455
+ }
456
+
457
+ # --------------------------
458
+ # 查询结果接口(同工具内可选)
459
+ # --------------------------
460
+ @mcp.tool(name="blast_check_task", description="Check BLAST task result by task_id")
461
+ def blast_check_task(payload: dict):
462
+ task_id = payload.get("task_id", "")
463
+ if not task_id:
464
+ return {"success": False, "result": None, "error": "task_id is required"}
465
+
466
+ task = BLAST_TASKS.get(task_id)
467
+ if not task:
468
+ return {"success": False, "result": None, "error": "task_id not found"}
469
+
470
+ return {"success": True, "result": task, "error": None}
471
 
472
 
473
  @mcp.tool(name="filter_blast_hits", description="Filter BLAST hits by identity percentage threshold")