一覧へ

「Bio-LLM MCPエージェント - モデルコンテキストプロトコルを使用して実験ツールを自律的にオーケストレーションする実用的な例」

「Anthropicモデルコンテキストプロトコル(MCP)標準を使用して、ClaudeエージェントがNCBI BLAST、UniProt、PubMedなどのバイオインフォマティクスWebサービスを自律的に呼び出す方法を示す、実用的なパイプライン。標準的なツール呼び出し、コンテキスト管理、エラー処理、および論文のメソッドを再現するための自動化されたループを網羅。バイオR&Dのための新しいインターフェース。」

上級
|
40
|
検証済み (2026-07)
進捗0/15 (0%)

Bio-LLM MCP エージェント — モデルコンテキストプロトコルを用いた自律的な実験ツールオーケストレーションの実用的なワークフロー

このセクションは、フェーズ4パイロットトラックの結論です。前のセクション(01臨床IE、02タンパク質埋め込み、03細胞セグメンテーション、11ボルツ-2構造と親和性)で、各ドメインとパイプラインのハードコアな実装を構築しました。しかし、現実世界のR&Dは、単一のパイプラインで終わるわけではありません。論文の読解、配列のクエリ、構造の検索、実験の設計、結果の再現、および障害の診断が含まれます。このセクションでは、標準化されたプロトコル(MCP)を使用して、これらの複数のツールをClaudeエージェントが自律的にオーケストレーションする、実用的なワークフローを構築します。

📚 推奨される事前学習(強く推奨されます)

このセクションは、AI×Bioの高度なトラックの結論部分です。先に続く前に、以下のDryBenchセクションを確認することを強くお勧めします。

事前資料を確認せずにこのセクションの実用的なコードを理解するのは難しくなります。なぜなら、エージェントループの原理、コンテキストの圧縮/要約戦略、およびClaudeコードにおけるツール利用の実用的なパターンを再説明せずに進むからです。


DryBenchで学んだこと

DryBench ai-native #9では、エージェントはLLM + ツール + ループ + メモリという4つのコンポーネントで定義され、各ツール呼び出しは、関数シグネチャに類似した強制的なスキーマによって管理されることを学びました。#10では、長いセッション中にコンテキストが圧縮およびオフロードされる方法を学びました。#14では、Claudeコードが、これらのすべての原理をCLIで実用的な方法で実装するツールであることを学びました。

しかし、現実世界のバイオR&Dでは、各ツールを個別に定義し、認証、リトライ、タイムアウト、およびレスポンスの解析を各プロジェクトで再実装するのは、非常に非効率的です。Anthropicが2024年11月に発表したモデルコンテキストプロトコル(MCP)は、この繰り返しを標準化するためのオープンプロトコルです[1]。単一のMCPサーバーは、同じセットのツールを複数のクライアント(Claude Desktop、Claude Code、Cursorなど)に公開し、サーバーとクライアントは、ツールを常に発見、呼び出し、およびレスポンスを処理します。このセクションでは、そのプロトコルをバイオドメインに実用的に適用します。

ハードコアな問題を定義する

実用的なR&Dシナリオ

研究者は、特定の疾患に関連する新しい候補遺伝子を特定しました。この遺伝子が本当に興味深いかどうかを判断するために、少なくとも次のタスクを実行する必要があります。

  1. 遺伝子配列をNCBI BLASTに送信して、相同遺伝子を見つけます。
  2. 最上位の結果のUniProt情報をクエリして、その機能をチェックします。
  3. 過去5年間の関連論文をPubMedで検索します。
  4. 最上位の論文の方法セクションを読み、再現可能かどうかを判断します。
  5. 再現可能な場合は、GitHubリポジトリをクローンし、環境をセットアップし、自分のデータに適応させ、結果を検証します。

過去には、このプロセスは、人がブラウザ、ターミナル、エディターの間を行き来しながら、各ステップを実行していました。このセクションの目標は、Claudeエージェントが5つのステップすべてを自律的に実行し、構造化されたレポートで結果を返すことです。このプロセスでは、人が重要な瞬間にのみ介入します(例:「この論文の方法を試してみるべきですか?」)。

なぜMCPが必要なのか?

MCPがなくても、上記のワークフローはツール利用のみで実装できます。ただし、MCPを使用すると、次の利点があります。

  • 再利用性: NCBI BLASTサーバーを作成すると、Claude Desktop、Claude Code、およびCursorを含むすべてのMCPクライアントが、すぐにそれを使用できます。
  • 標準化された認証、エラー、およびペイロード仕様: JSON-RPC 2.0に基づいています。各ドメインで再発明する必要はありません。
  • エコシステム: 他の人が作成したMCPサーバー(ファイルシステム、GitHub、Slackなど)を組み合わせて、複雑なエージェントをすぐに構築できます。
  • 分離されたプロセス: サーバーはstdin/stdoutまたはSSEを介して通信し、クラッシュをクライアントから分離します。

このセクションの目標メトリック

  • 単一のClaudeセッションで5つのステップすべてを自律的に実行する: 最大3回の人間による介入。
  • NCBI BLAST、UniProt、およびPubMed MCPサーバー: 既存のオープンコミュニティ実装を使用するか、最小限のカスタム実装を実装します。
  • エラーからの回復: レート制限、ネットワーク障害、または解析の失敗の場合に、指数関数的なバックオフで自動的にリトライします。
  • 再現性: セッションログ、ツール呼び出し履歴、および最終レポートは、後続の検証のためにJSON形式で保存されます。

ツールスタックとインフラストラクチャ要件

ツール役割ライセンス
mcp Python SDK (pip install mcp)MCPサーバー/クライアントの標準実装MIT (Anthropic公式)
Anthropic Claude API (anthropic)LLM + ツール使用オーケストレーター商用 (使用量に応じた課金)
BiopythonNCBI E-utilitiesラッパーBiopythonライセンス
requestsUniProt RESTクライアントApache 2.0
Docker (オプション)MCPサーバーのコンテナ化デプロイメントApache 2.0
Anthropic Skills (オプション)ドメイン固有のスキル登録Anthropicサービス

インフラストラクチャ要件:

  • GPUなしでも実行可能(LLMはAPIベースであり、ツールはウェブサービスです)。
  • ローカルCPUで、2コア、4GB以上のRAMが必要です。
  • ネットワーク: NCBI、UniProt、PubMed APIへのアクセスが必要です(レート制限を遵守する必要があります)。

学習者のための概算コスト: Claude APIの価格[2]に基づくと、単一のシナリオを最初から最後まで実行するには、約5ドルから20ドルかかります(ツールの呼び出し回数によって異なります)。個別のパブリックAPI(NCBI 3リクエスト/秒、UniProt制限なし、PubMed基本無料)を遵守すれば、追加のコストは発生しません。

実用的なパイプラインの実装

全体の流れ:

mermaid

ステップ1:最小限のMCPサーバーの実装 — UniProt RESTの例

MCPサーバーは、JSON-RPC 2.0プロセスであり、stdin / stdoutを介して通信します。 Python SDKは、ほとんどの定型処理を処理します[3]。

python
"""uniprot_mcp_server.py — UniProt REST APIをMCPツールとして公開します。
MCPサーバーは、複数のツールを公開できます。以下に3つの例を示します。
- search_protein:テキストクエリを使用してタンパク質を検索します。
- get_protein_details:アクセッション番号に基づいて詳細情報を取得します。
- get_sequence:アクセッション番号に基づいてFASTA形式の配列を取得します。
"""
import asyncio
import json
from typing import Any
import requests
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
UNIPROT_BASE = "https://rest.uniprot.org"
server = Server("uniprot-mcp")
@server.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="search_protein",
description="UniProtでテキストクエリを使用してタンパク質を検索します。上位K件の結果を返します。",
inputSchema={
"type": "object",
"properties": {
"query": {"type": "string", "description": "検索用語(例:'p53 human')"},
"limit": {"type": "integer", "default": 10},
},
"required": ["query"],
},
),
Tool(
name="get_protein_details",
description="UniProtのアクセッション番号を使用して、タンパク質の詳細情報を取得します。",
inputSchema={
"type": "object",
"properties": {"accession": {"type": "string"}},
"required": ["accession"],
},
),
Tool(
name="get_sequence",
description="UniProtアクセッション番号のFASTA配列を取得します。",
inputSchema={
"type": "object",
"properties": {"accession": {"type": "string"}},
"required": ["accession"],
},
),
]
@server.call_tool()
async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
"""ツール名に基づいて適切な関数にディスパッチします。実用的なシナリオでは、各関数を分離することをお勧めします。"""
try:
if name == "search_protein":
result = _search_protein(arguments["query"], arguments.get("limit", 10))
elif name == "get_protein_details":
result = _get_details(arguments["accession"])
elif name == "get_sequence":
result = _get_sequence(arguments["accession"])
else:
raise ValueError(f"不明なツール:{name}")
return [TextContent(type="text", text=json.dumps(result, ensure_ascii=False, indent=2))]
except requests.RequestException as e:
return [TextContent(type="text", text=json.dumps({"error": f"UniProt APIが失敗しました:{e}"}))]
except Exception as e:
return [TextContent(type="text", text=json.dumps({"error": str(e)}))]
def _search_protein(query: str, limit: int) -> dict:
resp = requests.get(
f"{UNIPROT_BASE}/uniprotkb/search",
params={
"query": query,
"format": "json",
"size": limit,
"fields": "accession,id,protein_name,organism_name,length",
},
timeout=30,
)
resp.raise_for_status()
results = resp.json().get("results", [])
return {"count": len(results), "results": results}
def _get_details(accession: str) -> dict:
resp = requests.get(
f"{UNIPROT_BASE}/uniprotkb/{accession}.json",
timeout=30,
)
resp.raise_for_status()
data = resp.json()
return {
"accession": data.get("primaryAccession"),
"protein_name": data.get("proteinDescription", {}),
"organism": data.get("organism", {}),
"length": data.get("sequence", {}).get("length"),
"function": [
c["texts"][0]["value"]
for c in data.get("comments", [])
if c.get("commentType") == "FUNCTION"
],
}
def _get_sequence(accession: str) -> dict:
resp = requests.get(f"{UNIPROT_BASE}/uniprotkb/{accession}.fasta", timeout=30)
resp.raise_for_status()
lines = resp.text.strip().split("\n")
return {
"accession": accession,
"header": lines[0],
"sequence": "".join(lines[1:]),
}
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream, server.create_initialization_options())
if __name__ == "__main__":
asyncio.run(main())

ステップ2:2番目のMCPサーバー — PubMed検索

python
"""pubmed_mcp_server.py — PubMed E-utilities用のMCPツール。"""
import asyncio
import json
from xml.etree import ElementTree as ET
import requests
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
EUTILS_BASE = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils"
server = Server("pubmed-mcp")
@server.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="search_pubmed",
description="検索語を使用して、PubMedで論文(PMID)のリストを検索します。",
inputSchema={
"type": "object",
"properties": {
"query": {"type": "string"},
"max_results": {"type": "integer", "default": 20},
"date_range_years": {"type": "integer", "default": 5},
},
"required": ["query"],
},
),
Tool(
name="fetch_abstracts",
description="PMIDのリストの詳細情報(抄録、著者、ジャーナル)を取得します。",
inputSchema={
"type": "object",
"properties": {
"pmids": {"type": "array", "items": {"type": "string"}},
},
"required": ["pmids"],
},
),
]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
try:
if name == "search_pubmed":
result = _search(arguments["query"], arguments.get("max_results", 20), arguments.get("date_range_years", 5))
elif name == "fetch_abstracts":
result = _fetch(arguments["pmids"])
else:
raise ValueError(f"不明なツール:{name}")
return [TextContent(type="text", text=json.dumps(result, ensure_ascii=False, indent=2))]
except Exception as e:
return [TextContent(type="text", text=json.dumps({"error": str(e)}))]
def _search(query: str, max_results: int, date_range_years: int) -> dict:
esearch = requests.get(
f"{EUTILS_BASE}/esearch.fcgi",
params={
"db": "pubmed",
"term": query,
"retmax": max_results,
"reldate": date_range_years * 365,
"datetype": "pdat",
"retmode": "json",
},
timeout=30,
)
esearch.raise_for_status()
pmids = esearch.json().get("esearchresult", {}).get("idlist", [])
return {"query": query, "count": len(pmids), "pmids": pmids}
def _fetch(pmids: list[str]) -> list[dict]:
if not pmids:
return []
efetch = requests.get(
f"{EUTILS_BASE}/efetch.fcgi",
params={"db": "pubmed", "id": ",".join(pmids), "rettype": "abstract", "retmode": "xml"},
timeout=60,
)
efetch.raise_for_status()
root = ET.fromstring(efetch.content)
articles = []
for art in root.findall(".//PubmedArticle"):
pmid = art.findtext(".//PMID")
title = art.findtext(".//ArticleTitle") or ""
abstract = " ".join(t.text or "" for t in art.findall(".//AbstractText"))
authors = [
f"{a.findtext('LastName') or ''} {a.findtext('Initials') or ''}".strip()
for a in art.findall(".//Author")
]
journal = art.findtext(".//Journal/Title") or ""
year = art.findtext(".//PubDate/Year") or ""
articles.append({
"pmid": pmid,
"title": title,
"abstract": abstract,
"authors": authors[:10],
"journal": journal,
"year": year,
})
return articles
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream, server.create_initialization_options())
if __name__ == "__main__":
asyncio.run(main())

ステップ3:NCBI BLAST MCPサーバー(概要)

BLASTは応答時間が長いため、非同期ポーリングパターンが必要です。

python
"""blast_mcp_server.py — NCBI BLAST QBlast(概要、コアロジックのみ)。"""
import time
import re
import requests
BLAST_URL = "https://blast.ncbi.nlm.nih.gov/Blast.cgi"
def submit_blast(sequence: str, program: str = "blastp", database: str = "nr") -> str:
"""QBlast RIDを返します。後でポーリングする必要があります。"""
resp = requests.post(BLAST_URL, data={
"CMD": "Put",
"PROGRAM": program,
"DATABASE": database,
"QUERY": sequence,
}, timeout=60)
resp.raise_for_status()
m = re.search(r"RID = (\S+)", resp.text)
if not m:
raise RuntimeError("QBlast RIDの解析に失敗しました")
return m.group(1)
def poll_blast(rid: str, poll_interval: int = 30, max_wait: int = 600) -> str:
"""BLASTの結果を待ちます。完了したらXMLを返します。"""
start = time.time()
while time.time() - start < max_wait:
resp = requests.get(BLAST_URL, params={"CMD": "Get", "RID": rid, "FORMAT_OBJECT": "SearchInfo"}, timeout=30)
if "Status=READY" in resp.text:
xml_resp = requests.get(BLAST_URL, params={"CMD": "Get", "RID": rid, "FORMAT_TYPE": "XML"}, timeout=60)
xml_resp.raise_for_status()
return xml_resp.text
elif "Status=FAILED" in resp.text:
raise RuntimeError(f"BLASTが失敗しました RID={rid}")
time.sleep(poll_interval)
raise TimeoutError(f"BLASTタイムアウト RID={rid}")
# MCPツールラッパーは、UniProtとPubMedサーバーと同じパターンに従います。コードは簡潔にするために省略されています。

ステップ4:Claude Agentクライアント — MCPオーケストレーション

Claude Agentは、複数のMCPサーバーに同時に接続し、ツールを自律的に呼び出します。

python
"""bio_agent.py — Claude Agent + 複数のMCPサーバーのオーケストレーション。"""
import asyncio
from contextlib import AsyncExitStack
import anthropic
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
class BioMCPAgent:
"""複数のMCPサーバーをオーケストレーションするClaude Agent。"""
def __init__(self, model: str = "claude-sonnet-4-5", max_turns: int = 20):
self.anthropic = anthropic.Anthropic()
self.model = model
self.max_turns = max_turns
self.sessions: dict[str, ClientSession] = {}
self.all_tools: list[dict] = []
self._exit_stack: AsyncExitStack | None = None
async def connect_server(self, name: str, command: str, args: list[str]) -> None:
"""MCPサーバーに接続します。プロセスを起動し、ツールリストを取得します。"""
params = StdioServerParameters(command=command, args=args)
stdio_transport = await self._exit_stack.enter_async_context(stdio_client(params))
session = await self._exit_stack.enter_async_context(ClientSession(*stdio_transport))
await session.initialize()
tools_result = await session.list_tools()
# Claudeのツール使用スキーマに変換します
for tool in tools_result.tools:
self.all_tools.append({
"name": f"{name}__{tool.name}", # サーバー名のプレフィックスを付けて競合を防ぎます
"description": tool.description,
"input_schema": tool.inputSchema,
})
self.sessions[f"{name}__{tool.name}"] = session
async def __aenter__(self):
self._exit_stack = AsyncExitStack()
await self._exit_stack.__aenter__()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self._exit_stack.__aexit__(exc_type, exc_val, exc_tb)
async def run(self, user_query: str, system_prompt: str) -> dict:
"""自律的なループを実行します。Claudeは、stop_reason="end_turn"になるまで繰り返します。"""
messages = [{"role": "user", "content": user_query}]
tool_log = []
for turn in range(self.max_turns):
response = self.anthropic.messages.create(
model=self.model,
max_tokens=4096,
system=system_prompt,
tools=self.all_tools,
messages=messages,
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason == "end_turn":
break
if response.stop_reason != "tool_use":
continue
# tool_useブロックを処理します
tool_results = []
for block in response.content:
if block.type != "tool_use":
continue
session = self.sessions.get(block.name)
if session is None:
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": f"不明なツール:{block.name}",
"is_error": True,
})
continue
try:
# サーバー名のプレフィックスを削除した元の名前を使用してツールを呼び出します
original_name = block.name.split("__", 1)[1]
result = await session.call_tool(original_name, block.input)
content_text = "\n".join(
c.text for c in result.content if hasattr(c, "text")
)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": content_text[:10000], # コンテキストを保存します
})
tool_log.append({
"tool": block.name,
"input": block.input,
"output_preview": content_text[:500],
})
except Exception as e:
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": f"ツール呼び出しに失敗しました:{e}",
"is_error": True,
})
messages.append({"role": "user", "content": tool_results})
# 最終的なテキストを抽出します
final_text = ""
for block in response.content:
if hasattr(block, "text"):
final_text += block.text
return {"final_answer": final_text, "tool_log": tool_log, "turns": turn + 1}
SYSTEM_PROMPT = """あなたはバイオインフォマティクスの研究アシスタントです。
ユーザーのリクエストを理解し、必要なMCPツール(uniprot__、pubmed__、blast__)を自律的に呼び出して、証拠に基づいて回答します。
原則:
1. 事実を捏造しないでください。すべての主張は、ツール呼び出しの結果によって裏付けられている必要があります。
2. 常に、回答にソース(UniProtアクセッション番号、PMIDなど)を含めてください。
3. ツールの呼び出しが失敗した場合は、代替方法を試みますが、ユーザーに失敗を通知します。
4. 不要なツール呼び出しを最小限に抑えます(コンテキストとコストを節約するため)。
"""
async def main():
async with BioMCPAgent() as agent:
await agent.connect_server("uniprot", "python", ["uniprot_mcp_server.py"])
await agent.connect_server("pubmed", "python", ["pubmed_mcp_server.py"])
# await agent.connect_server("blast", "python", ["blast_mcp_server.py"])
result = await agent.run(
user_query="最近の人間のTP53に関する上位3つの論文を要約し、それらをUniProtのこのタンパク質の機能注釈と比較して、新しい洞察があるかどうかを確認してください。",
system_prompt=SYSTEM_PROMPT,
)
print("=== 最終的な回答 ===")
print(result["final_answer"])
print(f"\n=== ターン数:{result['turns']} ===")
print(f"=== ツール呼び出し回数:{len(result['tool_log'])} ===")
for entry in result["tool_log"]:
print(f" - {entry['tool']}: {entry['input']}")
if __name__ == "__main__":
asyncio.run(main())

ステップ5:論文の再現ツールの吸収(セクション7-1のP-02を置き換え)

セクション7-1で、P-02(論文再現エージェント)をこのセクションに吸収することが決定されました。再現ツールは、別のMCPサーバーとして提供されます。

python
"""paper_repro_mcp_server.py — 論文の再現のためのツールのセット(概要)。"""
# ツール1:fetch_paper_pdf — DOIによるPDFの取得(オープンアクセスを優先します)
# ツール2:extract_method_section — PDFから方法セクションのテキストを抽出します
# ツール3:find_github_repo — 論文本文と補足資料からGitHub URLを検索します
# ツール4:clone_repo — Gitクローン(サンドボックスフォルダー内)
# ツール5:setup_env — requirements.txtまたはconda環境を検出し、インストールを試みます
# ツール6:run_reproduction — リポジトリの標準のエントリポイント(READMEまたはsetup.py)を実行します
# 各ツールは、UniProtとPubMedサーバーと同じMCPパターンに従います。
# 安全のため、クローンと実行は、別のコンテナー(Dockerサンドボックス)内で行う必要があります。

BioMCPAgentconnect_server("paper_repro", ...)を使用してこの再現サーバーツールを追加することで、論文の再現を含む自律的な実行ループが完了します。

Anthropic Skills の統合(オプションの拡張機能)

Anthropic Skills は、Claude Sonnet ファミリー内でドメイン固有のツールキットを登録する方法です [4]。上記の MCP サーバーの組み合わせを、単一の「バイオ研究」スキルとしてパッケージ化し、Claude Desktop または Claude Code CLI で簡単にアクティブにすることができます。

yaml
# skills/bio-research/skill.yaml
name: bio-research
version: 1.0.0
description: バイオインフォマティクス研究のための自律型エージェント。UniProt、PubMed、BLAST、および論文の再現を統合します。
mcp_servers:
  - name: uniprot
    command: python
    args: [/opt/mcp/uniprot_mcp_server.py]
  - name: pubmed
    command: python
    args: [/opt/mcp/pubmed_mcp_server.py]
  - name: blast
    command: python
    args: [/opt/mcp/blast_mcp_server.py]
  - name: paper_repro
    command: python
    args: [/opt/mcp/paper_repro_mcp_server.py]
system_prompt: |
  あなたはバイオインフォマティクス研究のアシスタントです。...

パフォーマンス、コスト、および既知の失敗事例

パフォーマンスの参考データ(公開されているデータに基づく)

アプローチシナリオ時間コストソース
手動(人間、ブラウジング)5ステップの遺伝子研究1-3時間0経験的なベースライン
Claude エージェント + ツール使用(MCP なし)同上5-10 分3-10 米ドルAnthropic ツール使用ベンチマーク [5]
Claude エージェント + MCP(この記事)同上3-7 分3-10 米ドルMCPmed 論文の証拠 [6]
GPT-4o + 関数呼び出し同上5-10 分5-15 米ドルOpenAI ドキュメント [7]

学習者向けの推定再現コスト

  • Claude API:シナリオあたり 5〜20 米ドル(ターンの数、ツールの繰り返し回数、および結果のサイズによって異なります)。
  • MCP サーバーのインフラストラクチャ:ローカルで実行する場合は無料。小規模な VPS 上にリモートでデプロイする場合は、月額約 5〜10 米ドル。
  • NCBI、UniProt、PubMed:無料(レート制限の対象となります)。

3 つの既知の失敗事例(コミュニティと論文から収集)

  1. NCBI BLAST のレート制限超過 症状:短い期間に BLAST QBlast を複数回呼び出すと、429 エラーまたは一時的なブロックが発生します。 原因:NCBI のポリシーでは、リクエストは 1 秒あたり 3 回(API キーが登録されている場合は 10 回/秒)に制限されています。 緩和策:(a)少なくとも time.sleep(1) の間隔を設ける、(b)NCBI API キーを登録し、api_key パラメーターとして渡す、(c)ピーク時間帯を避けてバッチ検索を実行することをお勧めします(夜間または週末)。 ソース:NCBI E-utilities ドキュメント「使用ガイドライン」[8]。

  2. MCP ツール名の衝突 症状:複数の MCP サーバーが同じツール名を公開している場合、クライアントはどちらを呼び出すべきか分かりません。 原因:MCP 仕様では、サーバー内のツール名のユニーク性のみが保証されます。 緩和策:コードに示すように、クライアント側でツール名を {server_name}__{tool_name} の形式でプレフィックスします。 ソース:MCP GitHub ディスカッション — 「ツール名の衝突」[9]。

  3. Claude エージェントがツールを誤って呼び出し、無限ループに陥る 症状:特定のタスクで、エージェントが同じツールを継続的に再呼び出しし、stop_reason="end_turn" に到達できません。 原因:(a)システムプロンプトで「いつ停止するか」の条件が明確に定義されていない、(b)ツールのレスポンスが常に部分的な回答のみを返す、(c)最大ターンの制限がない。 緩和策:(a)max_turns にハードリミットを設定する(このコードでは 20)、(b)システムプロンプトで、エージェントが「3 回の失敗後にユーザーに報告する」ことを明示的に記述する、(c)ツールの呼び出し履歴をコンテキストに要約して、繰り返しを検出する。 ソース:Anthropic Cookbook — エージェントのループパターン [10]。

拡張のアイデア

  • カスタム MCP サーバーシリーズ: PDB 構造ファイルを取得し、ChEMBL 化合物を検索し、GEO 転写データにアクセスし、Ensembl バリアントにアクセスするなど。パート 15 で続きます。
  • マルチエージェントコラボレーション: 研究、可視化、およびレポート作成のエージェントを個別のセッションに分割し、MCP を使用して結果を共有します。
  • リモート MCP サーバーのデプロイ: MCP サーバーを Cloudflare Workers、AWS Lambda、または Docker Swarm にホストして、複数のユーザー間で共有します。
  • ウェットラボとの統合: 実験の計画を自動化するために、実験機器(例:液体ハンドラーロボット)の API を MCP ツールとして公開します。

次のパート

  • パート 15 bio-mcp-server-suite: この記事のサーバーを拡張して、PDB、ChEMBL、および GEO を含む 5〜7 個のカスタム MCP サーバーのシリーズを構築します。
  • パート 09 llm-vendor-benchmark: 同じ MCP ツールセットを使用して、Claude、GPT-4o、および Gemini でのツール使用の精度を比較します。
  • パート 10 med-llm-reproduction: MCP ベースのベンチマークの再現を自動化します。
  • パート 01 clinical-notes-ie-llm: パート 01 の臨床 IE パイプラインを MCP ツールとして公開し、この記事のエージェントがそれを自律的に呼び出せるようにします。

参考文献

  1. Anthropic. "Model Context Protocol"。公式ドキュメント:https://modelcontextprotocol.io/ / アナウンスブログ:https://www.anthropic.com/news/model-context-protocol
  2. Anthropic Claude API の料金:https://www.anthropic.com/pricing
  3. MCP Python SDK:https://github.com/modelcontextprotocol/python-sdk
  4. Anthropic Skills のドキュメント:https://docs.anthropic.com/en/docs/build-with-claude/skills
  5. Anthropic Tool Use のドキュメント:https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/overview
  6. Wagner F, Bittrich S. "MCPmed: a call for Model Context Protocol-enabled bioinformatics web services for LLM-driven discovery"。Briefings in Bioinformatics 2026。https://academic.oup.com/bib/article/27/1/bbag076/8495038
  7. OpenAI の関数呼び出し:https://platform.openai.com/docs/guides/function-calling
  8. NCBI E-utilities の使用に関するガイドライン:https://www.ncbi.nlm.nih.gov/books/NBK25497/
  9. MCP GitHub Discussions:https://github.com/modelcontextprotocol/specification/discussions
  10. Anthropic Cookbook(エージェントパターン):https://github.com/anthropics/anthropic-cookbook
  11. UniProt REST API:https://www.uniprot.org/help/api
  12. NCBI BLAST QBlast:https://ncbi.github.io/blast-cloud/dev/api.html
  13. Biopython E-utilities ラッパー:https://biopython.org/wiki/EUtils
  14. MCP 公式サーバーレジストリ:https://github.com/modelcontextprotocol/servers
  15. Claude Code(Anthropic 公式):https://claude.com/product/claude-code

💬 質問・コメント

0件のコメント

ログインせずに投稿できます。ゲスト投稿は投稿者自身で編集・削除できません。

0/2000

読み込み中...