BioMCP
by acashmoney
- src
- tools
import { z } from "zod";
import {
makeApiRequest,
RCSB_PDB_DATA_API,
RCSB_PDB_SEARCH_API
} from "../utils.js";
import { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
// Define types for the API responses
interface SearchResult {
identifier: string;
score?: number;
title?: string;
}
interface SearchResponse {
result_set?: SearchResult[];
total_count?: number;
}
interface StructureCitation {
title?: string;
journal_abbrev?: string;
year?: number;
}
interface StructureData {
struct?: {
title?: string;
};
rcsb_primary_citation?: StructureCitation;
}
// Define the tool schema
export const searchDiseaseProteinsSchema = {
disease: z.string().describe("Disease name (e.g., 'covid', 'alzheimer's')"),
};
/**
* Search for proteins related to a disease
*/
export async function searchDiseaseProteins({ disease }: { disease: string }, extra: RequestHandlerExtra): Promise<CallToolResult> {
console.error(`DEBUG: Processing search-disease-proteins request for disease: ${disease}`);
// Build a proper search query based on the curl example
const searchQuery = {
query: {
type: "group",
nodes: [
{
type: "group",
nodes: [
{
type: "group",
nodes: [
{
type: "terminal",
service: "full_text",
parameters: {
value: disease
}
}
],
logical_operator: "and"
}
],
logical_operator: "and",
label: "full_text"
}
],
logical_operator: "and"
},
return_type: "entry",
request_options: {
paginate: {
start: 0,
rows: 25
},
results_content_type: [
"experimental"
],
sort: [
{
sort_by: "score",
direction: "desc"
}
],
scoring_strategy: "combined"
}
};
console.error(`DEBUG: Using POST request to search API`);
// Make a POST request with the properly structured query
const searchData = await makeApiRequest(RCSB_PDB_SEARCH_API, 'POST', searchQuery) as SearchResponse;
if (!searchData || !searchData.result_set) {
return {
content: [
{
type: "text",
text: `Failed to search for proteins related to: ${disease}. The search API might be temporarily unavailable.`,
},
],
};
}
// Check if we have results
const results = searchData.result_set || [];
console.error(`DEBUG: Found ${results.length} results for disease: ${disease}`);
if (results.length === 0) {
// Try alternative search with broader criteria
const altSearchQuery = {
query: {
type: "group",
nodes: [
{
type: "group",
nodes: [
{
type: "terminal",
service: "text",
parameters: {
attribute: "text",
operator: "contains_phrase",
value: disease
}
}
],
logical_operator: "and"
}
],
logical_operator: "and"
},
return_type: "entry",
request_options: {
paginate: {
start: 0,
rows: 25
},
sort: [
{
sort_by: "score",
direction: "desc"
}
]
}
};
console.error(`DEBUG: No results with specific search, trying broader search with POST request`);
const altSearchData = await makeApiRequest(RCSB_PDB_SEARCH_API, 'POST', altSearchQuery) as SearchResponse;
if (!altSearchData || !altSearchData.result_set || altSearchData.result_set.length === 0) {
return {
content: [
{
type: "text",
text: `No proteins found related to: ${disease}. Try using a different disease name or more general terms.`,
},
],
};
}
const altResults = altSearchData.result_set;
console.error(`DEBUG: Found ${altResults.length} results with broader search`);
let resultsText = `Found ${altResults.length} proteins that might be related to: "${disease}"\n\n`;
// Process the alternative results
const topResults = altResults.slice(0, 5);
for (const result of topResults) {
const pdbId = result.identifier;
console.error(`DEBUG: Fetching details for PDB ID: ${pdbId}`);
const structureUrl = `${RCSB_PDB_DATA_API}/core/entry/${pdbId}`;
const structureData = await makeApiRequest(structureUrl) as StructureData;
if (!structureData) {
resultsText += `PDB ID: ${pdbId} (Error fetching details)\n---\n\n`;
continue;
}
resultsText += `PDB ID: ${pdbId}\n`;
resultsText += `Title: ${structureData?.struct?.title || "Unknown"}\n`;
if (structureData?.rcsb_primary_citation) {
const citation = structureData.rcsb_primary_citation;
resultsText += `Publication: ${citation.title || "Unknown"} (${citation.journal_abbrev || "Unknown"}, ${citation.year || "Unknown"})\n`;
}
resultsText += "---\n\n";
}
resultsText += "To analyze any of these structures in detail, you can use the analyze-active-site tool with the PDB ID.";
return {
content: [
{
type: "text",
text: resultsText,
},
],
};
}
let resultsText = `Found ${results.length} proteins related to: "${disease}"\n\n`;
// Get detailed info for top 5 results
const topResults = results.slice(0, 5);
for (const result of topResults) {
const pdbId = result.identifier;
console.error(`DEBUG: Fetching details for PDB ID: ${pdbId}`);
const structureUrl = `${RCSB_PDB_DATA_API}/core/entry/${pdbId}`;
const structureData = await makeApiRequest(structureUrl) as StructureData;
if (!structureData) {
resultsText += `PDB ID: ${pdbId} (Error fetching details)\n---\n\n`;
continue;
}
resultsText += `PDB ID: ${pdbId}\n`;
resultsText += `Title: ${structureData?.struct?.title || "Unknown"}\n`;
if (structureData?.rcsb_primary_citation) {
const citation = structureData.rcsb_primary_citation;
resultsText += `Publication: ${citation.title || "Unknown"} (${citation.journal_abbrev || "Unknown"}, ${citation.year || "Unknown"})\n`;
}
resultsText += "---\n\n";
}
// Add note about how to analyze structures
resultsText += "To analyze any of these structures in detail, you can use the analyze-active-site tool with the PDB ID.";
return {
content: [
{
type: "text",
text: resultsText,
},
],
};
}