bioinformatics-mcp-server 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bioinformatics_mcp_server-0.1.0/.gitignore +44 -0
- bioinformatics_mcp_server-0.1.0/PKG-INFO +92 -0
- bioinformatics_mcp_server-0.1.0/README.md +69 -0
- bioinformatics_mcp_server-0.1.0/pyproject.toml +38 -0
- bioinformatics_mcp_server-0.1.0/smithery.yaml +17 -0
- bioinformatics_mcp_server-0.1.0/src/__init__.py +0 -0
- bioinformatics_mcp_server-0.1.0/src/server.py +45 -0
- bioinformatics_mcp_server-0.1.0/src/tools/__init__.py +0 -0
- bioinformatics_mcp_server-0.1.0/src/tools/genes.py +272 -0
- bioinformatics_mcp_server-0.1.0/src/tools/literature.py +223 -0
- bioinformatics_mcp_server-0.1.0/src/tools/proteins.py +242 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
.env
|
|
2
|
+
keys.env
|
|
3
|
+
.mcp.json
|
|
4
|
+
analytics.json
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.pyc
|
|
7
|
+
*.pyo
|
|
8
|
+
.venv/
|
|
9
|
+
venv/
|
|
10
|
+
*.log
|
|
11
|
+
*.egg-info/
|
|
12
|
+
dist/
|
|
13
|
+
build/
|
|
14
|
+
.claude/
|
|
15
|
+
|
|
16
|
+
# Andere MCP-Projekte (haben eigene Git-Repos)
|
|
17
|
+
Agriculture_MCP/
|
|
18
|
+
Analytics_MCP/
|
|
19
|
+
Aviation_MCP/
|
|
20
|
+
Company_MCP/
|
|
21
|
+
Cybersecurity_MCP/
|
|
22
|
+
Directory_MCP/
|
|
23
|
+
Germany_MCP/
|
|
24
|
+
Health_MCP/
|
|
25
|
+
Memory_MCP/
|
|
26
|
+
Payment_Gateway_MCP/
|
|
27
|
+
Reputation_MCP/
|
|
28
|
+
Space_MCP/
|
|
29
|
+
Weather_MCP/
|
|
30
|
+
Workflow_MCP/
|
|
31
|
+
Agent_Commerce_MCP/
|
|
32
|
+
Agent_Identity_MCP/
|
|
33
|
+
Business_Bridge_MCP/
|
|
34
|
+
github_profile/
|
|
35
|
+
API_Converter_MCP/
|
|
36
|
+
Agent_Coordination_MCP/
|
|
37
|
+
Agent_Interface_Standard/
|
|
38
|
+
Agent_Validator_MCP/
|
|
39
|
+
Feedback_MCP/
|
|
40
|
+
Hive_Mind_MCP/
|
|
41
|
+
MCP_Hub/
|
|
42
|
+
Prompt_Library_MCP/
|
|
43
|
+
Political_Finance_MCP/
|
|
44
|
+
Supply_Chain_MCP/
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bioinformatics-mcp-server
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP-Server für Bioinformatik-Daten — Gene, Proteine, PubMed-Literatur, Varianten und Sequenzen via NCBI & UniProt
|
|
5
|
+
Project-URL: Homepage, https://github.com/AiAgentKarl/bioinformatics-mcp-server
|
|
6
|
+
Project-URL: Repository, https://github.com/AiAgentKarl/bioinformatics-mcp-server
|
|
7
|
+
Project-URL: Issues, https://github.com/AiAgentKarl/bioinformatics-mcp-server/issues
|
|
8
|
+
Author: AiAgentKarl
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: ai-agent,bioinformatics,genes,genomics,life-science,mcp,ncbi,proteins,pubmed,uniprot
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: httpx>=0.27.0
|
|
20
|
+
Requires-Dist: mcp[cli]>=1.0.0
|
|
21
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# bioinformatics-mcp-server
|
|
25
|
+
|
|
26
|
+
MCP-Server für Bioinformatik- und Life-Science-Daten. Gibt AI-Agents Zugriff auf NCBI Gene, UniProt, PubMed und ClinVar — komplett kostenlos ohne API-Key.
|
|
27
|
+
|
|
28
|
+
## Tools
|
|
29
|
+
|
|
30
|
+
| Tool | Beschreibung |
|
|
31
|
+
|------|-------------|
|
|
32
|
+
| `search_genes` | Gen-Suche in NCBI Gene Database (Symbol, Name, Funktion) |
|
|
33
|
+
| `get_gene_details` | Detailinfos zu einem Gen via NCBI Gene ID |
|
|
34
|
+
| `search_clinvar_variants` | Klinische Varianten/Mutationen via NCBI ClinVar |
|
|
35
|
+
| `get_taxonomy` | Taxonomische Klassifikation via NCBI Taxonomy |
|
|
36
|
+
| `search_proteins` | Protein-Suche in UniProt |
|
|
37
|
+
| `get_protein_details` | Protein-Details via UniProt Accession |
|
|
38
|
+
| `get_ncbi_sequence` | DNA/Protein-Sequenzen aus NCBI (FASTA/GenBank) |
|
|
39
|
+
| `search_pubmed` | Literatur-Suche in PubMed |
|
|
40
|
+
| `get_pubmed_abstract` | Vollständiger Abstract eines PubMed-Artikels |
|
|
41
|
+
| `get_pubmed_citations` | Artikel die eine Publikation zitieren |
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install bioinformatics-mcp-server
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Konfiguration (Claude Desktop)
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"mcpServers": {
|
|
54
|
+
"bioinformatics": {
|
|
55
|
+
"command": "bioinformatics-server"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Beispiele
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
# Gen-Suche
|
|
65
|
+
search_genes("BRCA1", organism="human")
|
|
66
|
+
search_genes("p53", organism="mouse")
|
|
67
|
+
|
|
68
|
+
# Klinische Varianten
|
|
69
|
+
search_clinvar_variants("CFTR", significance="pathogenic")
|
|
70
|
+
|
|
71
|
+
# Protein-Lookup
|
|
72
|
+
search_proteins("insulin receptor", organism="human")
|
|
73
|
+
get_protein_details("P06213")
|
|
74
|
+
|
|
75
|
+
# DNA-Sequenz abrufen
|
|
76
|
+
get_ncbi_sequence("NM_007294", database="nucleotide")
|
|
77
|
+
|
|
78
|
+
# Literatur
|
|
79
|
+
search_pubmed("CRISPR gene therapy cancer 2025", max_results=5)
|
|
80
|
+
get_pubmed_abstract("33461210")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## APIs
|
|
84
|
+
|
|
85
|
+
- **NCBI E-utilities** — Gene, ClinVar, Taxonomy, PubMed, Sequenzen (kostenlos, 3 req/s)
|
|
86
|
+
- **UniProt REST API** — Proteine, Sequenzen, Funktionen (kostenlos, unbegrenzt)
|
|
87
|
+
|
|
88
|
+
Kein API-Key erforderlich.
|
|
89
|
+
|
|
90
|
+
## Lizenz
|
|
91
|
+
|
|
92
|
+
MIT
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# bioinformatics-mcp-server
|
|
2
|
+
|
|
3
|
+
MCP-Server für Bioinformatik- und Life-Science-Daten. Gibt AI-Agents Zugriff auf NCBI Gene, UniProt, PubMed und ClinVar — komplett kostenlos ohne API-Key.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
| Tool | Beschreibung |
|
|
8
|
+
|------|-------------|
|
|
9
|
+
| `search_genes` | Gen-Suche in NCBI Gene Database (Symbol, Name, Funktion) |
|
|
10
|
+
| `get_gene_details` | Detailinfos zu einem Gen via NCBI Gene ID |
|
|
11
|
+
| `search_clinvar_variants` | Klinische Varianten/Mutationen via NCBI ClinVar |
|
|
12
|
+
| `get_taxonomy` | Taxonomische Klassifikation via NCBI Taxonomy |
|
|
13
|
+
| `search_proteins` | Protein-Suche in UniProt |
|
|
14
|
+
| `get_protein_details` | Protein-Details via UniProt Accession |
|
|
15
|
+
| `get_ncbi_sequence` | DNA/Protein-Sequenzen aus NCBI (FASTA/GenBank) |
|
|
16
|
+
| `search_pubmed` | Literatur-Suche in PubMed |
|
|
17
|
+
| `get_pubmed_abstract` | Vollständiger Abstract eines PubMed-Artikels |
|
|
18
|
+
| `get_pubmed_citations` | Artikel die eine Publikation zitieren |
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install bioinformatics-mcp-server
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Konfiguration (Claude Desktop)
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"bioinformatics": {
|
|
32
|
+
"command": "bioinformatics-server"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Beispiele
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
# Gen-Suche
|
|
42
|
+
search_genes("BRCA1", organism="human")
|
|
43
|
+
search_genes("p53", organism="mouse")
|
|
44
|
+
|
|
45
|
+
# Klinische Varianten
|
|
46
|
+
search_clinvar_variants("CFTR", significance="pathogenic")
|
|
47
|
+
|
|
48
|
+
# Protein-Lookup
|
|
49
|
+
search_proteins("insulin receptor", organism="human")
|
|
50
|
+
get_protein_details("P06213")
|
|
51
|
+
|
|
52
|
+
# DNA-Sequenz abrufen
|
|
53
|
+
get_ncbi_sequence("NM_007294", database="nucleotide")
|
|
54
|
+
|
|
55
|
+
# Literatur
|
|
56
|
+
search_pubmed("CRISPR gene therapy cancer 2025", max_results=5)
|
|
57
|
+
get_pubmed_abstract("33461210")
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## APIs
|
|
61
|
+
|
|
62
|
+
- **NCBI E-utilities** — Gene, ClinVar, Taxonomy, PubMed, Sequenzen (kostenlos, 3 req/s)
|
|
63
|
+
- **UniProt REST API** — Proteine, Sequenzen, Funktionen (kostenlos, unbegrenzt)
|
|
64
|
+
|
|
65
|
+
Kein API-Key erforderlich.
|
|
66
|
+
|
|
67
|
+
## Lizenz
|
|
68
|
+
|
|
69
|
+
MIT
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "bioinformatics-mcp-server"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "MCP-Server für Bioinformatik-Daten — Gene, Proteine, PubMed-Literatur, Varianten und Sequenzen via NCBI & UniProt"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [{ name = "AiAgentKarl" }]
|
|
13
|
+
keywords = ["mcp", "bioinformatics", "genomics", "ncbi", "uniprot", "pubmed", "genes", "proteins", "ai-agent", "life-science"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Bio-Informatics",
|
|
21
|
+
"Topic :: Software Development :: Libraries",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"mcp[cli]>=1.0.0",
|
|
25
|
+
"httpx>=0.27.0",
|
|
26
|
+
"python-dotenv>=1.0.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/AiAgentKarl/bioinformatics-mcp-server"
|
|
31
|
+
Repository = "https://github.com/AiAgentKarl/bioinformatics-mcp-server"
|
|
32
|
+
Issues = "https://github.com/AiAgentKarl/bioinformatics-mcp-server/issues"
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
bioinformatics-server = "src.server:main"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
packages = ["src"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: bioinformatics-mcp-server
|
|
2
|
+
description: MCP-Server for bioinformatics data — genes, proteins, PubMed literature, clinical variants and sequences via NCBI & UniProt (free, no API key required)
|
|
3
|
+
license: MIT
|
|
4
|
+
homepage: https://github.com/AiAgentKarl/bioinformatics-mcp-server
|
|
5
|
+
|
|
6
|
+
startCommand:
|
|
7
|
+
type: stdio
|
|
8
|
+
configSchema:
|
|
9
|
+
type: object
|
|
10
|
+
properties: {}
|
|
11
|
+
required: []
|
|
12
|
+
commandFunction: |-
|
|
13
|
+
(config) => ({
|
|
14
|
+
command: "bioinformatics-server",
|
|
15
|
+
args: [],
|
|
16
|
+
env: {}
|
|
17
|
+
})
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Bioinformatics MCP Server — Genomik- und Life-Science-Daten für AI-Agents.
|
|
2
|
+
|
|
3
|
+
Bietet:
|
|
4
|
+
- Gen-Suche und -Details via NCBI Gene Database
|
|
5
|
+
- Protein-Lookup via UniProt REST API
|
|
6
|
+
- Wissenschaftliche Literatur via PubMed (NCBI)
|
|
7
|
+
- Klinische Varianten via NCBI ClinVar
|
|
8
|
+
- DNA/Protein-Sequenzen via NCBI Nucleotide
|
|
9
|
+
- Taxonomie-Lookup via NCBI Taxonomy
|
|
10
|
+
Alle Daten kostenlos, kein API-Key erforderlich.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from mcp.server.fastmcp import FastMCP
|
|
14
|
+
|
|
15
|
+
from src.tools.genes import register_gene_tools
|
|
16
|
+
from src.tools.proteins import register_protein_tools
|
|
17
|
+
from src.tools.literature import register_literature_tools
|
|
18
|
+
|
|
19
|
+
# FastMCP Server erstellen
|
|
20
|
+
mcp = FastMCP(
|
|
21
|
+
"Bioinformatics MCP Server",
|
|
22
|
+
instructions=(
|
|
23
|
+
"Gibt AI-Agents Zugriff auf bioinformatische Datenbanken: "
|
|
24
|
+
"NCBI Gene (Gen-Suche, Funktion, Orthologe), "
|
|
25
|
+
"UniProt (Protein-Struktur, Funktion, Sequenzen), "
|
|
26
|
+
"PubMed (wissenschaftliche Literatur, Abstracts), "
|
|
27
|
+
"NCBI ClinVar (klinische Varianten, Pathogenität), "
|
|
28
|
+
"NCBI Taxonomy (Spezies-Klassifikation, Phylogenie). "
|
|
29
|
+
"Alle Endpunkte kostenlos ohne API-Key via NCBI E-utilities und UniProt REST API."
|
|
30
|
+
),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Tool-Gruppen registrieren
|
|
34
|
+
register_gene_tools(mcp)
|
|
35
|
+
register_protein_tools(mcp)
|
|
36
|
+
register_literature_tools(mcp)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def main():
|
|
40
|
+
"""Server starten."""
|
|
41
|
+
mcp.run(transport="stdio")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""Gen-Tools — Suche und Details via NCBI Gene Database und ClinVar."""
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# NCBI E-utilities Basis-URL
|
|
8
|
+
NCBI_BASE = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils"
|
|
9
|
+
# Tool- und Email-Parameter für NCBI (höflicher Zugriff)
|
|
10
|
+
NCBI_PARAMS = {"tool": "bioinformatics-mcp-server", "email": "agent@aiagentkarl.com"}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def _ncbi_get(endpoint: str, params: dict) -> dict[str, Any]:
|
|
14
|
+
"""NCBI E-utilities API Anfrage."""
|
|
15
|
+
try:
|
|
16
|
+
merged = {**NCBI_PARAMS, **params, "retmode": "json"}
|
|
17
|
+
async with httpx.AsyncClient(timeout=20.0) as client:
|
|
18
|
+
response = await client.get(f"{NCBI_BASE}/{endpoint}", params=merged)
|
|
19
|
+
response.raise_for_status()
|
|
20
|
+
return response.json()
|
|
21
|
+
except Exception as e:
|
|
22
|
+
return {"error": str(e)}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def _ncbi_fetch_text(endpoint: str, params: dict) -> str:
|
|
26
|
+
"""NCBI E-utilities API Anfrage mit Text-Rückgabe (für Sequenzen)."""
|
|
27
|
+
try:
|
|
28
|
+
merged = {**NCBI_PARAMS, **params}
|
|
29
|
+
async with httpx.AsyncClient(timeout=20.0) as client:
|
|
30
|
+
response = await client.get(f"{NCBI_BASE}/{endpoint}", params=merged)
|
|
31
|
+
response.raise_for_status()
|
|
32
|
+
return response.text
|
|
33
|
+
except Exception as e:
|
|
34
|
+
return f"Fehler: {e}"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def register_gene_tools(mcp) -> None:
|
|
38
|
+
"""Registriert alle Gen- und Varianten-Tools."""
|
|
39
|
+
|
|
40
|
+
@mcp.tool()
|
|
41
|
+
async def search_genes(
|
|
42
|
+
query: str,
|
|
43
|
+
organism: str = "human",
|
|
44
|
+
max_results: int = 10,
|
|
45
|
+
) -> dict:
|
|
46
|
+
"""Sucht nach Genen in der NCBI Gene Datenbank.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
query: Suchbegriff (z.B. "BRCA1", "TP53", "insulin receptor")
|
|
50
|
+
organism: Organismus (z.B. "human", "mouse", "rat"). Standard: human
|
|
51
|
+
max_results: Maximale Ergebnisanzahl (1-20). Standard: 10
|
|
52
|
+
"""
|
|
53
|
+
max_results = min(max(1, max_results), 20)
|
|
54
|
+
# Suchanfrage mit Organismus-Filter
|
|
55
|
+
search_term = f"{query}[Gene Name] AND {organism}[Organism]"
|
|
56
|
+
|
|
57
|
+
# Schritt 1: IDs suchen
|
|
58
|
+
search_data = await _ncbi_get(
|
|
59
|
+
"esearch.fcgi",
|
|
60
|
+
{"db": "gene", "term": search_term, "retmax": max_results},
|
|
61
|
+
)
|
|
62
|
+
if "error" in search_data:
|
|
63
|
+
return {"fehler": search_data["error"]}
|
|
64
|
+
|
|
65
|
+
ids = search_data.get("esearchresult", {}).get("idlist", [])
|
|
66
|
+
if not ids:
|
|
67
|
+
# Breiteren Fallback versuchen
|
|
68
|
+
search_data = await _ncbi_get(
|
|
69
|
+
"esearch.fcgi",
|
|
70
|
+
{"db": "gene", "term": f"{query} AND {organism}[Organism]", "retmax": max_results},
|
|
71
|
+
)
|
|
72
|
+
ids = search_data.get("esearchresult", {}).get("idlist", [])
|
|
73
|
+
|
|
74
|
+
if not ids:
|
|
75
|
+
return {"fehler": f"Keine Gene für '{query}' in '{organism}' gefunden"}
|
|
76
|
+
|
|
77
|
+
# Schritt 2: Zusammenfassungen abrufen
|
|
78
|
+
summary_data = await _ncbi_get(
|
|
79
|
+
"esummary.fcgi",
|
|
80
|
+
{"db": "gene", "id": ",".join(ids)},
|
|
81
|
+
)
|
|
82
|
+
if "error" in summary_data:
|
|
83
|
+
return {"fehler": summary_data["error"]}
|
|
84
|
+
|
|
85
|
+
result_map = summary_data.get("result", {})
|
|
86
|
+
gene_list = []
|
|
87
|
+
for gene_id in ids:
|
|
88
|
+
g = result_map.get(gene_id, {})
|
|
89
|
+
if not g or gene_id == "uids":
|
|
90
|
+
continue
|
|
91
|
+
gene_list.append({
|
|
92
|
+
"gen_id": gene_id,
|
|
93
|
+
"name": g.get("name", ""),
|
|
94
|
+
"vollstaendiger_name": g.get("description", ""),
|
|
95
|
+
"organismus": g.get("organism", {}).get("scientificname", ""),
|
|
96
|
+
"chromosom": g.get("chromosome", ""),
|
|
97
|
+
"position": g.get("maplocation", ""),
|
|
98
|
+
"zusammenfassung": (g.get("summary") or "")[:400],
|
|
99
|
+
"ncbi_url": f"https://www.ncbi.nlm.nih.gov/gene/{gene_id}",
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
"suchbegriff": query,
|
|
104
|
+
"organismus": organism,
|
|
105
|
+
"gefunden": len(gene_list),
|
|
106
|
+
"gene": gene_list,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@mcp.tool()
|
|
110
|
+
async def get_gene_details(gene_id: str) -> dict:
|
|
111
|
+
"""Ruft detaillierte Informationen zu einem Gen via NCBI Gene ID ab.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
gene_id: NCBI Gene ID (z.B. "672" für BRCA1, "7157" für TP53)
|
|
115
|
+
"""
|
|
116
|
+
data = await _ncbi_get("esummary.fcgi", {"db": "gene", "id": gene_id})
|
|
117
|
+
if "error" in data:
|
|
118
|
+
return {"fehler": data["error"]}
|
|
119
|
+
|
|
120
|
+
result = data.get("result", {}).get(gene_id, {})
|
|
121
|
+
if not result:
|
|
122
|
+
return {"fehler": f"Gen mit ID '{gene_id}' nicht gefunden"}
|
|
123
|
+
|
|
124
|
+
# Verwandte Gene (Gene Neighbors) aus LocationHistList
|
|
125
|
+
orthologe = []
|
|
126
|
+
for orth in result.get("orthologs", [])[:5]:
|
|
127
|
+
orthologe.append({
|
|
128
|
+
"gen_id": orth.get("geneids", [""])[0],
|
|
129
|
+
"organismus": orth.get("scientificname", ""),
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
"gen_id": gene_id,
|
|
134
|
+
"symbol": result.get("name", ""),
|
|
135
|
+
"vollstaendiger_name": result.get("description", ""),
|
|
136
|
+
"organismus": result.get("organism", {}).get("scientificname", ""),
|
|
137
|
+
"chromosom": result.get("chromosome", ""),
|
|
138
|
+
"position": result.get("maplocation", ""),
|
|
139
|
+
"typ": result.get("genetype", ""),
|
|
140
|
+
"zusammenfassung": result.get("summary", ""),
|
|
141
|
+
"aliases": result.get("otheraliases", ""),
|
|
142
|
+
"andere_bezeichnungen": result.get("otherdesignations", ""),
|
|
143
|
+
"ncbi_url": f"https://www.ncbi.nlm.nih.gov/gene/{gene_id}",
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@mcp.tool()
|
|
147
|
+
async def search_clinvar_variants(
|
|
148
|
+
gene_symbol: str,
|
|
149
|
+
significance: str = "pathogenic",
|
|
150
|
+
max_results: int = 10,
|
|
151
|
+
) -> dict:
|
|
152
|
+
"""Sucht klinische Varianten (Mutationen) für ein Gen via NCBI ClinVar.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
gene_symbol: Gen-Symbol (z.B. "BRCA1", "TP53", "CFTR")
|
|
156
|
+
significance: Klinische Signifikanz — "pathogenic", "benign", "uncertain" oder "all". Standard: pathogenic
|
|
157
|
+
max_results: Maximale Ergebnisanzahl (1-20). Standard: 10
|
|
158
|
+
"""
|
|
159
|
+
max_results = min(max(1, max_results), 20)
|
|
160
|
+
|
|
161
|
+
# Suchanfrage aufbauen
|
|
162
|
+
if significance == "all":
|
|
163
|
+
term = f"{gene_symbol}[gene]"
|
|
164
|
+
else:
|
|
165
|
+
sig_map = {
|
|
166
|
+
"pathogenic": "Pathogenic[ClinSig]",
|
|
167
|
+
"benign": "Benign[ClinSig]",
|
|
168
|
+
"uncertain": "Uncertain significance[ClinSig]",
|
|
169
|
+
}
|
|
170
|
+
sig_filter = sig_map.get(significance, "Pathogenic[ClinSig]")
|
|
171
|
+
term = f"{gene_symbol}[gene] AND {sig_filter}"
|
|
172
|
+
|
|
173
|
+
# IDs suchen
|
|
174
|
+
search_data = await _ncbi_get(
|
|
175
|
+
"esearch.fcgi",
|
|
176
|
+
{"db": "clinvar", "term": term, "retmax": max_results},
|
|
177
|
+
)
|
|
178
|
+
if "error" in search_data:
|
|
179
|
+
return {"fehler": search_data["error"]}
|
|
180
|
+
|
|
181
|
+
ids = search_data.get("esearchresult", {}).get("idlist", [])
|
|
182
|
+
if not ids:
|
|
183
|
+
return {
|
|
184
|
+
"fehler": f"Keine Varianten für '{gene_symbol}' mit Signifikanz '{significance}' gefunden",
|
|
185
|
+
"tipp": "Versuche significance='all' für alle Varianten",
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
# Zusammenfassungen abrufen
|
|
189
|
+
summary_data = await _ncbi_get(
|
|
190
|
+
"esummary.fcgi",
|
|
191
|
+
{"db": "clinvar", "id": ",".join(ids)},
|
|
192
|
+
)
|
|
193
|
+
if "error" in summary_data:
|
|
194
|
+
return {"fehler": summary_data["error"]}
|
|
195
|
+
|
|
196
|
+
result_map = summary_data.get("result", {})
|
|
197
|
+
varianten = []
|
|
198
|
+
for var_id in ids:
|
|
199
|
+
v = result_map.get(var_id, {})
|
|
200
|
+
if not v or var_id == "uids":
|
|
201
|
+
continue
|
|
202
|
+
|
|
203
|
+
# Signifikanz aus clinical_significance
|
|
204
|
+
clin_sig = v.get("clinical_significance", {})
|
|
205
|
+
sig_label = clin_sig.get("description", "")
|
|
206
|
+
|
|
207
|
+
varianten.append({
|
|
208
|
+
"varianten_id": var_id,
|
|
209
|
+
"name": v.get("title", ""),
|
|
210
|
+
"gen": gene_symbol,
|
|
211
|
+
"typ": v.get("obj_type", ""),
|
|
212
|
+
"klinische_signifikanz": sig_label,
|
|
213
|
+
"review_status": clin_sig.get("review_status", ""),
|
|
214
|
+
"bedingung": v.get("trait_set", [{}])[0].get("trait_name", "") if v.get("trait_set") else "",
|
|
215
|
+
"letzte_bewertung": clin_sig.get("last_evaluated", ""),
|
|
216
|
+
"clinvar_url": f"https://www.ncbi.nlm.nih.gov/clinvar/variation/{var_id}/",
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
"gen": gene_symbol,
|
|
221
|
+
"signifikanz_filter": significance,
|
|
222
|
+
"gefunden": len(varianten),
|
|
223
|
+
"varianten": varianten,
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
@mcp.tool()
|
|
227
|
+
async def get_taxonomy(
|
|
228
|
+
taxon: str,
|
|
229
|
+
) -> dict:
|
|
230
|
+
"""Ruft taxonomische Klassifikation einer Spezies via NCBI Taxonomy ab.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
taxon: Artname oder Taxonomie-ID (z.B. "Homo sapiens", "9606", "Mus musculus")
|
|
234
|
+
"""
|
|
235
|
+
# Prüfen ob ID oder Name
|
|
236
|
+
if taxon.isdigit():
|
|
237
|
+
tax_id = taxon
|
|
238
|
+
else:
|
|
239
|
+
# Name zu ID auflösen
|
|
240
|
+
search_data = await _ncbi_get(
|
|
241
|
+
"esearch.fcgi",
|
|
242
|
+
{"db": "taxonomy", "term": taxon},
|
|
243
|
+
)
|
|
244
|
+
if "error" in search_data:
|
|
245
|
+
return {"fehler": search_data["error"]}
|
|
246
|
+
ids = search_data.get("esearchresult", {}).get("idlist", [])
|
|
247
|
+
if not ids:
|
|
248
|
+
return {"fehler": f"Taxonomie für '{taxon}' nicht gefunden"}
|
|
249
|
+
tax_id = ids[0]
|
|
250
|
+
|
|
251
|
+
# Details abrufen
|
|
252
|
+
summary_data = await _ncbi_get(
|
|
253
|
+
"esummary.fcgi",
|
|
254
|
+
{"db": "taxonomy", "id": tax_id},
|
|
255
|
+
)
|
|
256
|
+
if "error" in summary_data:
|
|
257
|
+
return {"fehler": summary_data["error"]}
|
|
258
|
+
|
|
259
|
+
t = summary_data.get("result", {}).get(tax_id, {})
|
|
260
|
+
if not t:
|
|
261
|
+
return {"fehler": f"Taxonomie-ID '{tax_id}' nicht gefunden"}
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
"taxonomie_id": tax_id,
|
|
265
|
+
"wissenschaftlicher_name": t.get("scientificname", ""),
|
|
266
|
+
"gemeiner_name": t.get("commonname", ""),
|
|
267
|
+
"rang": t.get("rank", ""),
|
|
268
|
+
"abteilung": t.get("division", ""),
|
|
269
|
+
"genetischer_code": t.get("gencodeId", ""),
|
|
270
|
+
"mitochondrialer_code": t.get("mgcodeId", ""),
|
|
271
|
+
"ncbi_url": f"https://www.ncbi.nlm.nih.gov/Taxonomy/Browser/wwwtax.cgi?id={tax_id}",
|
|
272
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""Literatur-Tools — Wissenschaftliche Artikel via PubMed (NCBI E-utilities)."""
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# NCBI E-utilities Basis-URL
|
|
8
|
+
NCBI_BASE = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils"
|
|
9
|
+
NCBI_PARAMS = {"tool": "bioinformatics-mcp-server", "email": "agent@aiagentkarl.com"}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def _ncbi_get(endpoint: str, params: dict) -> dict[str, Any]:
|
|
13
|
+
"""NCBI E-utilities API Anfrage."""
|
|
14
|
+
try:
|
|
15
|
+
merged = {**NCBI_PARAMS, **params, "retmode": "json"}
|
|
16
|
+
async with httpx.AsyncClient(timeout=20.0) as client:
|
|
17
|
+
response = await client.get(f"{NCBI_BASE}/{endpoint}", params=merged)
|
|
18
|
+
response.raise_for_status()
|
|
19
|
+
return response.json()
|
|
20
|
+
except Exception as e:
|
|
21
|
+
return {"error": str(e)}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def register_literature_tools(mcp) -> None:
|
|
25
|
+
"""Registriert alle Literatur-Tools."""
|
|
26
|
+
|
|
27
|
+
@mcp.tool()
|
|
28
|
+
async def search_pubmed(
|
|
29
|
+
query: str,
|
|
30
|
+
max_results: int = 10,
|
|
31
|
+
sort: str = "relevance",
|
|
32
|
+
years_back: int = 0,
|
|
33
|
+
) -> dict:
|
|
34
|
+
"""Sucht wissenschaftliche Artikel in PubMed.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
query: Suchbegriff (z.B. "BRCA1 breast cancer therapy", "CRISPR gene editing")
|
|
38
|
+
max_results: Maximale Ergebnisanzahl (1-20). Standard: 10
|
|
39
|
+
sort: Sortierung — "relevance" oder "date". Standard: relevance
|
|
40
|
+
years_back: Nur Artikel der letzten N Jahre (0 = alle). Standard: 0
|
|
41
|
+
"""
|
|
42
|
+
max_results = min(max(1, max_results), 20)
|
|
43
|
+
|
|
44
|
+
# Datumsfilter
|
|
45
|
+
search_term = query
|
|
46
|
+
if years_back > 0:
|
|
47
|
+
from datetime import datetime
|
|
48
|
+
aktuelles_jahr = datetime.now().year
|
|
49
|
+
von_jahr = aktuelles_jahr - years_back
|
|
50
|
+
search_term = f"{query} AND ({von_jahr}:{aktuelles_jahr}[pdat])"
|
|
51
|
+
|
|
52
|
+
sort_param = "relevance" if sort == "relevance" else "pub+date"
|
|
53
|
+
|
|
54
|
+
# Schritt 1: Artikel-IDs suchen
|
|
55
|
+
search_data = await _ncbi_get(
|
|
56
|
+
"esearch.fcgi",
|
|
57
|
+
{
|
|
58
|
+
"db": "pubmed",
|
|
59
|
+
"term": search_term,
|
|
60
|
+
"retmax": max_results,
|
|
61
|
+
"sort": sort_param,
|
|
62
|
+
"usehistory": "y",
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
if "error" in search_data:
|
|
66
|
+
return {"fehler": search_data["error"]}
|
|
67
|
+
|
|
68
|
+
ids = search_data.get("esearchresult", {}).get("idlist", [])
|
|
69
|
+
count = search_data.get("esearchresult", {}).get("count", "0")
|
|
70
|
+
|
|
71
|
+
if not ids:
|
|
72
|
+
return {
|
|
73
|
+
"fehler": f"Keine Artikel für '{query}' in PubMed gefunden",
|
|
74
|
+
"tipp": "Versuche weniger spezifische Suchbegriffe oder Englisch",
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Schritt 2: Zusammenfassungen abrufen
|
|
78
|
+
summary_data = await _ncbi_get(
|
|
79
|
+
"esummary.fcgi",
|
|
80
|
+
{"db": "pubmed", "id": ",".join(ids)},
|
|
81
|
+
)
|
|
82
|
+
if "error" in summary_data:
|
|
83
|
+
return {"fehler": summary_data["error"]}
|
|
84
|
+
|
|
85
|
+
result_map = summary_data.get("result", {})
|
|
86
|
+
artikel = []
|
|
87
|
+
|
|
88
|
+
for pmid in ids:
|
|
89
|
+
a = result_map.get(pmid, {})
|
|
90
|
+
if not a or pmid == "uids":
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
# Autoren extrahieren
|
|
94
|
+
autoren = []
|
|
95
|
+
for author in a.get("authors", [])[:5]:
|
|
96
|
+
autoren.append(author.get("name", ""))
|
|
97
|
+
|
|
98
|
+
artikel.append({
|
|
99
|
+
"pmid": pmid,
|
|
100
|
+
"titel": a.get("title", ""),
|
|
101
|
+
"autoren": autoren,
|
|
102
|
+
"journal": a.get("fulljournalname", a.get("source", "")),
|
|
103
|
+
"erscheinungsjahr": a.get("pubdate", "")[:4],
|
|
104
|
+
"doi": a.get("elocationid", ""),
|
|
105
|
+
"abstract_url": f"https://pubmed.ncbi.nlm.nih.gov/{pmid}/",
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
"suchbegriff": query,
|
|
110
|
+
"gesamt_treffer": int(count),
|
|
111
|
+
"angezeigt": len(artikel),
|
|
112
|
+
"sortierung": sort,
|
|
113
|
+
"artikel": artikel,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@mcp.tool()
|
|
117
|
+
async def get_pubmed_abstract(pmid: str) -> dict:
|
|
118
|
+
"""Ruft den vollständigen Abstract eines PubMed-Artikels ab.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
pmid: PubMed ID (z.B. "33461210", "36257307")
|
|
122
|
+
"""
|
|
123
|
+
# Abstract via efetch abrufen
|
|
124
|
+
try:
|
|
125
|
+
merged = {**NCBI_PARAMS, "db": "pubmed", "id": pmid, "rettype": "abstract", "retmode": "text"}
|
|
126
|
+
async with httpx.AsyncClient(timeout=20.0) as client:
|
|
127
|
+
response = await client.get(f"{NCBI_BASE}/efetch.fcgi", params=merged)
|
|
128
|
+
response.raise_for_status()
|
|
129
|
+
abstract_text = response.text
|
|
130
|
+
except Exception as e:
|
|
131
|
+
return {"fehler": str(e)}
|
|
132
|
+
|
|
133
|
+
# Zusätzliche Metadaten
|
|
134
|
+
summary_data = await _ncbi_get("esummary.fcgi", {"db": "pubmed", "id": pmid})
|
|
135
|
+
meta = summary_data.get("result", {}).get(pmid, {})
|
|
136
|
+
|
|
137
|
+
autoren = []
|
|
138
|
+
for author in meta.get("authors", []):
|
|
139
|
+
autoren.append(author.get("name", ""))
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
"pmid": pmid,
|
|
143
|
+
"titel": meta.get("title", ""),
|
|
144
|
+
"autoren": autoren,
|
|
145
|
+
"journal": meta.get("fulljournalname", meta.get("source", "")),
|
|
146
|
+
"erscheinungsdatum": meta.get("pubdate", ""),
|
|
147
|
+
"doi": meta.get("elocationid", ""),
|
|
148
|
+
"abstract": abstract_text.strip(),
|
|
149
|
+
"pubmed_url": f"https://pubmed.ncbi.nlm.nih.gov/{pmid}/",
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@mcp.tool()
|
|
153
|
+
async def get_pubmed_citations(
|
|
154
|
+
pmid: str,
|
|
155
|
+
max_results: int = 10,
|
|
156
|
+
) -> dict:
|
|
157
|
+
"""Ruft Artikel ab, die einen bestimmten PubMed-Artikel zitieren.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
pmid: PubMed ID des zitierten Artikels (z.B. "33461210")
|
|
161
|
+
max_results: Maximale Ergebnisanzahl (1-20). Standard: 10
|
|
162
|
+
"""
|
|
163
|
+
max_results = min(max(1, max_results), 20)
|
|
164
|
+
|
|
165
|
+
# elink: Artikel finden die diesen zitieren
|
|
166
|
+
try:
|
|
167
|
+
params = {
|
|
168
|
+
**NCBI_PARAMS,
|
|
169
|
+
"dbfrom": "pubmed",
|
|
170
|
+
"db": "pubmed",
|
|
171
|
+
"id": pmid,
|
|
172
|
+
"linkname": "pubmed_pubmed_citedin",
|
|
173
|
+
"retmode": "json",
|
|
174
|
+
}
|
|
175
|
+
async with httpx.AsyncClient(timeout=20.0) as client:
|
|
176
|
+
response = await client.get(f"{NCBI_BASE}/elink.fcgi", params=params)
|
|
177
|
+
response.raise_for_status()
|
|
178
|
+
link_data = response.json()
|
|
179
|
+
except Exception as e:
|
|
180
|
+
return {"fehler": str(e)}
|
|
181
|
+
|
|
182
|
+
# IDs aus linksets extrahieren
|
|
183
|
+
citing_ids = []
|
|
184
|
+
for linkset in link_data.get("linksets", []):
|
|
185
|
+
for lsd in linkset.get("linksetdbs", []):
|
|
186
|
+
if lsd.get("linkname") == "pubmed_pubmed_citedin":
|
|
187
|
+
citing_ids = lsd.get("links", [])[:max_results]
|
|
188
|
+
break
|
|
189
|
+
|
|
190
|
+
if not citing_ids:
|
|
191
|
+
return {
|
|
192
|
+
"pmid": pmid,
|
|
193
|
+
"zitiert_von": 0,
|
|
194
|
+
"artikel": [],
|
|
195
|
+
"hinweis": "Keine Zitationen in PubMed gefunden (nur indexierte Artikel werden erfasst)",
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# Zusammenfassungen der zitierenden Artikel
|
|
199
|
+
id_str = ",".join(str(i) for i in citing_ids)
|
|
200
|
+
summary_data = await _ncbi_get("esummary.fcgi", {"db": "pubmed", "id": id_str})
|
|
201
|
+
result_map = summary_data.get("result", {})
|
|
202
|
+
|
|
203
|
+
artikel = []
|
|
204
|
+
for cid in citing_ids:
|
|
205
|
+
cid_str = str(cid)
|
|
206
|
+
a = result_map.get(cid_str, {})
|
|
207
|
+
if not a or cid_str == "uids":
|
|
208
|
+
continue
|
|
209
|
+
autoren = [auth.get("name", "") for auth in a.get("authors", [])[:3]]
|
|
210
|
+
artikel.append({
|
|
211
|
+
"pmid": cid_str,
|
|
212
|
+
"titel": a.get("title", ""),
|
|
213
|
+
"autoren": autoren,
|
|
214
|
+
"journal": a.get("fulljournalname", a.get("source", "")),
|
|
215
|
+
"erscheinungsjahr": a.get("pubdate", "")[:4],
|
|
216
|
+
"url": f"https://pubmed.ncbi.nlm.nih.gov/{cid_str}/",
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
"original_pmid": pmid,
|
|
221
|
+
"gefundene_zitationen": len(artikel),
|
|
222
|
+
"artikel": artikel,
|
|
223
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Protein-Tools — Lookup und Sequenzen via UniProt REST API."""
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# UniProt REST API Basis-URL
|
|
8
|
+
UNIPROT_BASE = "https://rest.uniprot.org/uniprotkb"
|
|
9
|
+
# NCBI E-utilities für Sequenzen
|
|
10
|
+
NCBI_BASE = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils"
|
|
11
|
+
NCBI_PARAMS = {"tool": "bioinformatics-mcp-server", "email": "agent@aiagentkarl.com"}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def _uniprot_get(endpoint: str, params: dict = None) -> dict[str, Any]:
|
|
15
|
+
"""UniProt REST API Anfrage."""
|
|
16
|
+
try:
|
|
17
|
+
async with httpx.AsyncClient(
|
|
18
|
+
timeout=20.0,
|
|
19
|
+
headers={"Accept": "application/json"},
|
|
20
|
+
) as client:
|
|
21
|
+
response = await client.get(
|
|
22
|
+
f"{UNIPROT_BASE}/{endpoint}",
|
|
23
|
+
params=params or {},
|
|
24
|
+
)
|
|
25
|
+
response.raise_for_status()
|
|
26
|
+
return response.json()
|
|
27
|
+
except Exception as e:
|
|
28
|
+
return {"error": str(e)}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def _ncbi_fetch_text(endpoint: str, params: dict) -> str:
|
|
32
|
+
"""NCBI E-utilities Text-Anfrage für Sequenzen."""
|
|
33
|
+
try:
|
|
34
|
+
merged = {**NCBI_PARAMS, **params}
|
|
35
|
+
async with httpx.AsyncClient(timeout=20.0) as client:
|
|
36
|
+
response = await client.get(f"{NCBI_BASE}/{endpoint}", params=merged)
|
|
37
|
+
response.raise_for_status()
|
|
38
|
+
return response.text
|
|
39
|
+
except Exception as e:
|
|
40
|
+
return f"Fehler: {e}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def register_protein_tools(mcp) -> None:
|
|
44
|
+
"""Registriert alle Protein-Tools."""
|
|
45
|
+
|
|
46
|
+
@mcp.tool()
|
|
47
|
+
async def search_proteins(
|
|
48
|
+
query: str,
|
|
49
|
+
organism: str = "human",
|
|
50
|
+
max_results: int = 10,
|
|
51
|
+
) -> dict:
|
|
52
|
+
"""Sucht nach Proteinen in der UniProt-Datenbank.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
query: Suchbegriff (z.B. "BRCA1", "insulin receptor", "p53 tumor suppressor")
|
|
56
|
+
organism: Organismus (z.B. "human", "mouse", "E. coli"). Standard: human
|
|
57
|
+
max_results: Maximale Ergebnisanzahl (1-25). Standard: 10
|
|
58
|
+
"""
|
|
59
|
+
max_results = min(max(1, max_results), 25)
|
|
60
|
+
|
|
61
|
+
# UniProt Suchanfrage
|
|
62
|
+
search_query = f"({query}) AND (organism_name:{organism})"
|
|
63
|
+
data = await _uniprot_get(
|
|
64
|
+
"search",
|
|
65
|
+
{
|
|
66
|
+
"query": search_query,
|
|
67
|
+
"size": max_results,
|
|
68
|
+
"fields": "accession,id,protein_name,gene_names,organism_name,length,reviewed,function",
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
if "error" in data:
|
|
72
|
+
return {"fehler": data["error"]}
|
|
73
|
+
|
|
74
|
+
results = data.get("results", [])
|
|
75
|
+
if not results:
|
|
76
|
+
return {"fehler": f"Keine Proteine für '{query}' in '{organism}' gefunden"}
|
|
77
|
+
|
|
78
|
+
proteine = []
|
|
79
|
+
for p in results:
|
|
80
|
+
# Proteinname extrahieren
|
|
81
|
+
prot_names = p.get("proteinDescription", {})
|
|
82
|
+
rec_name = prot_names.get("recommendedName", {})
|
|
83
|
+
full_name = rec_name.get("fullName", {}).get("value", "")
|
|
84
|
+
if not full_name:
|
|
85
|
+
sub_names = prot_names.get("submittedNames", [])
|
|
86
|
+
if sub_names:
|
|
87
|
+
full_name = sub_names[0].get("fullName", {}).get("value", "")
|
|
88
|
+
|
|
89
|
+
# Gen-Namen
|
|
90
|
+
gene_names = []
|
|
91
|
+
for gn in p.get("genes", []):
|
|
92
|
+
gn_val = gn.get("geneName", {}).get("value", "")
|
|
93
|
+
if gn_val:
|
|
94
|
+
gene_names.append(gn_val)
|
|
95
|
+
|
|
96
|
+
# Funktion (erstes Kommentar)
|
|
97
|
+
funktion = ""
|
|
98
|
+
for kommentar in p.get("comments", []):
|
|
99
|
+
if kommentar.get("commentType") == "FUNCTION":
|
|
100
|
+
texts = kommentar.get("texts", [])
|
|
101
|
+
if texts:
|
|
102
|
+
funktion = texts[0].get("value", "")[:300]
|
|
103
|
+
break
|
|
104
|
+
|
|
105
|
+
proteine.append({
|
|
106
|
+
"accession": p.get("primaryAccession", ""),
|
|
107
|
+
"entry_name": p.get("uniProtkbId", ""),
|
|
108
|
+
"protein_name": full_name,
|
|
109
|
+
"gene_namen": gene_names,
|
|
110
|
+
"organismus": p.get("organism", {}).get("scientificName", ""),
|
|
111
|
+
"laenge": p.get("sequence", {}).get("length", 0),
|
|
112
|
+
"reviewed": p.get("entryType", "") == "UniProtKB reviewed (Swiss-Prot)",
|
|
113
|
+
"funktion": funktion,
|
|
114
|
+
"uniprot_url": f"https://www.uniprot.org/uniprotkb/{p.get('primaryAccession', '')}",
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
"suchbegriff": query,
|
|
119
|
+
"organismus": organism,
|
|
120
|
+
"gefunden": len(proteine),
|
|
121
|
+
"proteine": proteine,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@mcp.tool()
|
|
125
|
+
async def get_protein_details(accession: str) -> dict:
|
|
126
|
+
"""Ruft detaillierte Informationen zu einem Protein via UniProt Accession ab.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
accession: UniProt Accession-Nummer (z.B. "P38398" für BRCA1, "P04637" für TP53)
|
|
130
|
+
"""
|
|
131
|
+
data = await _uniprot_get(accession)
|
|
132
|
+
if "error" in data:
|
|
133
|
+
return {"fehler": data["error"]}
|
|
134
|
+
|
|
135
|
+
# Protein-Name
|
|
136
|
+
prot_names = data.get("proteinDescription", {})
|
|
137
|
+
rec_name = prot_names.get("recommendedName", {})
|
|
138
|
+
full_name = rec_name.get("fullName", {}).get("value", "")
|
|
139
|
+
kurz_name = rec_name.get("shortNames", [{}])[0].get("value", "") if rec_name.get("shortNames") else ""
|
|
140
|
+
|
|
141
|
+
# Gen-Namen
|
|
142
|
+
gene_namen = []
|
|
143
|
+
for gn in data.get("genes", []):
|
|
144
|
+
gn_val = gn.get("geneName", {}).get("value", "")
|
|
145
|
+
if gn_val:
|
|
146
|
+
gene_namen.append(gn_val)
|
|
147
|
+
|
|
148
|
+
# Funktion
|
|
149
|
+
funktion = ""
|
|
150
|
+
subunit = ""
|
|
151
|
+
for kommentar in data.get("comments", []):
|
|
152
|
+
ct = kommentar.get("commentType", "")
|
|
153
|
+
texts = kommentar.get("texts", [])
|
|
154
|
+
if ct == "FUNCTION" and texts and not funktion:
|
|
155
|
+
funktion = texts[0].get("value", "")[:500]
|
|
156
|
+
elif ct == "SUBUNIT" and texts and not subunit:
|
|
157
|
+
subunit = texts[0].get("value", "")[:300]
|
|
158
|
+
|
|
159
|
+
# Keywords
|
|
160
|
+
keywords = [kw.get("name", "") for kw in data.get("keywords", [])[:10]]
|
|
161
|
+
|
|
162
|
+
# Sequenz-Info
|
|
163
|
+
seq = data.get("sequence", {})
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
"accession": accession,
|
|
167
|
+
"entry_name": data.get("uniProtkbId", ""),
|
|
168
|
+
"protein_name": full_name,
|
|
169
|
+
"kurzname": kurz_name,
|
|
170
|
+
"gene_namen": gene_namen,
|
|
171
|
+
"organismus": data.get("organism", {}).get("scientificName", ""),
|
|
172
|
+
"taxonomie_id": data.get("organism", {}).get("taxonId", ""),
|
|
173
|
+
"laenge_aminosaeuren": seq.get("length", 0),
|
|
174
|
+
"molekulargewicht": seq.get("molWeight", 0),
|
|
175
|
+
"funktion": funktion,
|
|
176
|
+
"untereinheit": subunit,
|
|
177
|
+
"keywords": keywords,
|
|
178
|
+
"reviewed": data.get("entryType", "") == "UniProtKB reviewed (Swiss-Prot)",
|
|
179
|
+
"sequenz": (seq.get("value", "") or "")[:200] + ("..." if len(seq.get("value", "") or "") > 200 else ""),
|
|
180
|
+
"uniprot_url": f"https://www.uniprot.org/uniprotkb/{accession}",
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@mcp.tool()
|
|
184
|
+
async def get_ncbi_sequence(
|
|
185
|
+
accession: str,
|
|
186
|
+
database: str = "nucleotide",
|
|
187
|
+
sequence_type: str = "fasta",
|
|
188
|
+
) -> dict:
|
|
189
|
+
"""Ruft eine DNA- oder Protein-Sequenz aus NCBI ab.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
accession: NCBI Accession-Nummer (z.B. "NM_007294" für BRCA1 mRNA, "NP_009225" für BRCA1 Protein)
|
|
193
|
+
database: Datenbank — "nucleotide" (DNA/RNA) oder "protein". Standard: nucleotide
|
|
194
|
+
sequence_type: Format — "fasta" (Sequenz) oder "gb" (GenBank-Eintrag). Standard: fasta
|
|
195
|
+
"""
|
|
196
|
+
valid_dbs = ("nucleotide", "protein")
|
|
197
|
+
if database not in valid_dbs:
|
|
198
|
+
return {"fehler": f"Ungültige Datenbank. Erlaubt: {', '.join(valid_dbs)}"}
|
|
199
|
+
|
|
200
|
+
valid_types = ("fasta", "gb")
|
|
201
|
+
if sequence_type not in valid_types:
|
|
202
|
+
return {"fehler": f"Ungültiger Sequenztyp. Erlaubt: {', '.join(valid_types)}"}
|
|
203
|
+
|
|
204
|
+
# Zuerst ID suchen
|
|
205
|
+
params = {**NCBI_PARAMS, "retmode": "json"}
|
|
206
|
+
try:
|
|
207
|
+
async with httpx.AsyncClient(timeout=20.0) as client:
|
|
208
|
+
search_resp = await client.get(
|
|
209
|
+
f"{NCBI_BASE}/esearch.fcgi",
|
|
210
|
+
params={**params, "db": database, "term": accession},
|
|
211
|
+
)
|
|
212
|
+
search_resp.raise_for_status()
|
|
213
|
+
search_data = search_resp.json()
|
|
214
|
+
except Exception as e:
|
|
215
|
+
return {"fehler": str(e)}
|
|
216
|
+
|
|
217
|
+
ids = search_data.get("esearchresult", {}).get("idlist", [])
|
|
218
|
+
if not ids:
|
|
219
|
+
return {"fehler": f"Sequenz '{accession}' in '{database}' nicht gefunden"}
|
|
220
|
+
|
|
221
|
+
# Sequenz abrufen
|
|
222
|
+
ret_type = "fasta" if sequence_type == "fasta" else "gb"
|
|
223
|
+
sequenz = await _ncbi_fetch_text(
|
|
224
|
+
"efetch.fcgi",
|
|
225
|
+
{"db": database, "id": ids[0], "rettype": ret_type, "retmode": "text"},
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# FASTA kürzen für Ausgabe
|
|
229
|
+
zeilen = sequenz.strip().split("\n")
|
|
230
|
+
header = zeilen[0] if zeilen else ""
|
|
231
|
+
seq_daten = "".join(zeilen[1:]) if len(zeilen) > 1 else ""
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
"accession": accession,
|
|
235
|
+
"datenbank": database,
|
|
236
|
+
"format": sequence_type,
|
|
237
|
+
"ncbi_id": ids[0],
|
|
238
|
+
"header": header,
|
|
239
|
+
"sequenz_laenge": len(seq_daten),
|
|
240
|
+
"sequenz_vorschau": seq_daten[:300] + ("..." if len(seq_daten) > 300 else ""),
|
|
241
|
+
"vollstaendige_sequenz": seq_daten if len(seq_daten) <= 2000 else seq_daten[:2000] + f"... [{len(seq_daten) - 2000} weitere Zeichen]",
|
|
242
|
+
}
|