Skip to main content
Glama
blast.ts8.54 kB
import { z } from "zod"; import { BaseTool } from "./base.js"; import { getParserForTool } from "../lib/parsers.js"; export class BlastSubmitTool extends BaseTool { register(): void { this.context.server.tool( "blast_submit", "Submit sequences for BLAST similarity searching against NCBI databases. Supports all BLAST programs (blastn, blastp, blastx, tblastn, tblastx) with customizable parameters. Returns job ID for result retrieval.", { cmd: z.literal("Put").describe("Command to submit search"), query: z .string() .describe("Search query (FASTA sequence, accession, or GI)"), database: z .string() .describe("BLAST database name (e.g., nt, nr, swissprot)"), program: z .enum(["blastn", "blastp", "blastx", "tblastn", "tblastx"]) .describe("BLAST program"), megablast: z .enum(["on", "off"]) .optional() .describe("Enable megablast for blastn"), expect: z .number() .optional() .default(10) .describe("Expect value threshold"), filter: z .string() .optional() .describe("Low complexity filtering (L, F, or m+filter)"), word_size: z .number() .optional() .describe("Word size for initial matches"), gapcosts: z .string() .optional() .describe("Gap existence and extension costs (space-separated)"), matrix: z .enum([ "BLOSUM45", "BLOSUM50", "BLOSUM62", "BLOSUM80", "BLOSUM90", "PAM250", "PAM30", "PAM70", ]) .optional() .describe("Scoring matrix"), nucl_reward: z .number() .optional() .describe("Reward for matching nucleotides"), nucl_penalty: z .number() .optional() .describe("Penalty for mismatching nucleotides"), hitlist_size: z .number() .optional() .default(100) .describe("Number of database sequences to keep"), format_type: z .enum(["HTML", "Text", "XML2", "XML2_S", "JSON2", "JSON2_S", "SAM"]) .optional() .default("XML2") .describe("Output format"), descriptions: z .number() .optional() .default(100) .describe("Number of descriptions to show"), alignments: z .number() .optional() .default(100) .describe("Number of alignments to show"), }, async ({ cmd, query, database, program, megablast, expect, filter, word_size, gapcosts, matrix, nucl_reward, nucl_penalty, hitlist_size, format_type, descriptions, alignments, }) => { try { if (!query || query.trim() === "") { throw new Error("Query sequence cannot be empty"); } const params = new URLSearchParams({ CMD: cmd, QUERY: query.trim(), DATABASE: database, PROGRAM: program, EMAIL: this.context.defaultEmail, TOOL: this.context.defaultTool, }); // Add optional parameters if (megablast) params.append("MEGABLAST", megablast); if (expect !== undefined) params.append("EXPECT", expect.toString()); if (filter) params.append("FILTER", filter); if (word_size !== undefined) params.append("WORD_SIZE", word_size.toString()); if (gapcosts) params.append("GAPCOSTS", gapcosts); if (matrix) params.append("MATRIX", matrix); if (nucl_reward !== undefined) params.append("NUCL_REWARD", nucl_reward.toString()); if (nucl_penalty !== undefined) params.append("NUCL_PENALTY", nucl_penalty.toString()); if (hitlist_size !== undefined) params.append("HITLIST_SIZE", hitlist_size.toString()); if (format_type) params.append("FORMAT_TYPE", format_type); if (descriptions !== undefined) params.append("DESCRIPTIONS", descriptions.toString()); if (alignments !== undefined) params.append("ALIGNMENTS", alignments.toString()); const url = `https://blast.ncbi.nlm.nih.gov/Blast.cgi?${params}`; const response = await fetch(url, { method: "POST" }); const data = await this.parseResponse(response, "BLAST Submit"); // Parse the BLAST response to extract structured data const parser = getParserForTool("BLAST Submit", data); const parseResult = parser.parse(data); if (parseResult.entities.length > 0) { const jobData = parseResult.entities[0].data; return { content: [ { type: "text", text: `✅ **BLAST Job Successfully Submitted**\n\n🆔 **Request ID (RID)**: \`${jobData.rid}\`\n⏱️ **Estimated Time**: ${jobData.estimated_time ? `${jobData.estimated_time} seconds` : "Unknown"}\n📊 **Status**: ${jobData.status}\n\n## 🚀 Next Steps:\nUse **\`blast_get\`** with RID \`${jobData.rid}\` to retrieve results when ready.\n\n💡 **Pro tip**: BLAST jobs typically take 15-30 seconds for small queries.`, }, ], }; } // Fallback to raw response if parsing failed return { content: [ { type: "text", text: `BLAST Submit Results:\n\n${this.formatResponseData(data)}`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error in BLAST Submit: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } }, ); } } export class BlastGetTool extends BaseTool { register(): void { this.context.server.tool( "blast_get", "Retrieve results from a submitted BLAST job using the Request ID. Get detailed sequence alignments, similarity scores, and annotations. Multiple output formats available including XML, JSON, and tabular.", { cmd: z.literal("Get").describe("Command to get results"), rid: z.string().describe("Request ID from BLAST submission"), format_type: z .enum(["HTML", "Text", "XML2", "XML2_S", "JSON2", "JSON2_S", "SAM"]) .optional() .default("XML2") .describe("Output format"), descriptions: z .number() .optional() .default(100) .describe("Number of descriptions to show"), alignments: z .number() .optional() .default(100) .describe("Number of alignments to show"), alignment_view: z .enum(["Pairwise", "QueryAnchored", "FlatQueryAnchored", "Tabular"]) .optional() .describe("Alignment view format"), }, async ({ cmd, rid, format_type, descriptions, alignments, alignment_view, }) => { try { if (!rid || rid.trim() === "") { throw new Error("Request ID (RID) cannot be empty"); } const params = new URLSearchParams({ CMD: cmd, RID: rid.trim(), }); if (format_type) params.append("FORMAT_TYPE", format_type); if (descriptions !== undefined) params.append("DESCRIPTIONS", descriptions.toString()); if (alignments !== undefined) params.append("ALIGNMENTS", alignments.toString()); if (alignment_view) params.append("ALIGNMENT_VIEW", alignment_view); const url = `https://blast.ncbi.nlm.nih.gov/Blast.cgi?${params}`; // Implement polling for BLAST results const maxRetries = 15; const retryDelay = 10000; // 10 seconds for (let i = 0; i < maxRetries; i++) { const response = await fetch(url); const data = await this.parseResponse(response, "BLAST Get"); // Check if the search is still running if ( data.includes("Status=WAITING") || data.includes("Status=UNKNOWN") ) { if (i < maxRetries - 1) { // Wait before the next attempt await new Promise((resolve) => setTimeout(resolve, retryDelay)); continue; } else { return { content: [ { type: "text", text: `BLAST search with RID ${rid} is still running after ${maxRetries} attempts. Please try again later.\n\n${this.formatResponseData(data)}`, }, ], }; } } // If results are ready or an error occurred, return the response return { content: [ { type: "text", text: `BLAST Results:\n\n${this.formatResponseData(data)}`, }, ], }; } // This should not be reached, but as a fallback: throw new Error("BLAST polling failed unexpectedly."); } catch (error) { return { content: [ { type: "text", text: `Error in BLAST Get: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } }, ); } }

Latest Blog Posts

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/QuentinCody/entrez-mcp-server'

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