
Foldseek Structural Search
- 1.1k installs
- 2.6k repo stars
- Updated July 7, 2026
- google-deepmind/science-skills
Foldseek Structural Search is a Claude Code skill that runs fast structural searches of protein structures against Foldseek databases for developers who need similarity lookup from PDB or mmCIF files inside agent workflo
About
Foldseek Structural Search is a google-deepmind/science-skills script skill that runs Foldseek search for a PDB or mmCIF file against structural databases directly from an agent workflow. The bundled script requires Python >=3.10 and executes Foldseek similarity queries as part of computational biology pipelines. Developers reach for Foldseek Structural Search when protein structure files need fast database scanning without manually chaining shell commands and parsing outputs. The skill targets structural bioinformatics engineers embedding Foldseek into automated research and annotation flows.
- Runs Foldseek searches for PDB/mmCIF files against 9 public databases including AFDB50, PDB100, and CATH50.
- Respects strict 0.1 queries-per-second rate limit using a shared HTTP client.
- Constructs manual multipart/form-data payloads for reliable API submission.
- Returns alignment hits with configurable maximum of 300 results and default E-value of 1000.
- Designed as a reusable CLI and agent skill for structural biology tasks.
Foldseek Structural Search by the numbers
- 1,130 all-time installs (skills.sh)
- +53 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #274 of 2,064 Data Science & ML skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/google-deepmind/science-skills --skill foldseek-structural-searchAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.1k |
|---|---|
| repo stars | ★ 2.6k |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 7, 2026 |
| Repository | google-deepmind/science-skills ↗ |
How do you search protein structures with Foldseek?
Run fast structural searches of protein structures against Foldseek databases directly from their agent workflow.
Who is it for?
Structural bioinformatics developers who need agent-driven Foldseek searches against protein structure databases from PDB or mmCIF inputs.
Skip if: Developers working on sequence-only genomics pipelines without structural PDB or mmCIF files.
When should I use this skill?
A developer provides a PDB or mmCIF structure and asks for Foldseek similarity search against structural databases.
What you get
Foldseek search results, structural similarity hits, and parsed output from PDB/mmCIF queries.
- Foldseek search results
- structural similarity hit lists
By the numbers
- Bundled script requires Python >=3.10
Files
Prerequisites
1. `uv`: Read the uv skill and follow its Setup instructions to ensure uv is installed and on PATH. 2. User Notification: If LICENSE_NOTIFICATION.txt does not already exist in this skill directory then (1) prominently notify the user to check the terms at https://search.foldseek.com/search and https://github.com/steineggerlab/foldseek, then (2) create the file recording the notification text and timestamp.
Goal
Submit a user-provided 3D protein structure file (.cif, .mmcif, or .pdb) to the Foldseek web server API to find structurally similar proteins. Report the top structural hits, interpret key alignment metrics, summarize the inferred protein functions, save the Markdown-formatted table to a .md file, and save the full detailed results to a local JSON file.
Core Rules
- File Requirement: This tool absolutely cannot search by sequence, name,
or accession ID. It strictly requires a .pdb, .cif, or .mmcif file path.
- Strict Validation: Never bypass the input validation or the database
allowlist check.
- Do Not Parse the JSON: Rely entirely on the generated
.mdfile for
your immediate summary. The JSON is saved purely for subsequent, specialized tool use.
- No Raw Parsing: Do not attempt to parse or read the raw 3D coordinates
yourself; always pass the file to the script.
- Notification: If this skill is used, ensure this is mentioned in the
output.
Instructions
1. Strict Input Validation: Verify that the user has explicitly provided a valid path to a .cif, .mmcif, or .pdb file in their workspace.
- If the user provided a protein name, an amino acid sequence, or an
accession ID (e.g., a UniProt ID) but NO downloaded structure file, halt immediately. Do not run the script.
- Inform the user that Foldseek requires a physical 3D coordinate file,
and suggest downloading the structure first (e.g., using the AlphaFold fetch tool). 2. Database Validation: Check if the user requested specific databases to search.
- Allowed List:
afdb50,afdb-swissprot,pdb100,BFVD,
mgnify_esm30, cath50, gmgcl_id, bfmd, afdb-proteome.
- If the user requests a database NOT on this list, halt immediately.
Do not run the script. Inform the user that the database is unsupported and provide them with the allowed list. 3. Generate File Names: Generate descriptive output file names for both the JSON data and the Markdown table based on the input file (e.g., proteinA_foldseek_results.json and proteinA_foldseek_results.md). 4. Execute the python script based on the user's request, redirecting the standard output into your generated .md file:
- Default (No databases specified): `uv run scripts/search.py
<path-to-file> -o <generated-filename.json> > <generated-filename.md>`
- Custom (Valid databases specified): `uv run scripts/search.py
<path-to-file> -o <generated-filename.json> --databases <db1,db2,db3> > <generated-filename.md> 5. The script will query the databases, save the full JSON payload, and write a Markdown-formatted table to your specified .md file. 6. **Read the Results:** Open and read the newly generated .md` file carefully to view the Markdown table. 7. Interpret the Metrics: Summarize the top 3 to 5 structural matches that have meaningfull annotations for the user. When reporting, assess the match quality using these specific fields:
- Prob (Probability): Values approaching 1.0 (100%) indicate extreme
confidence that the fold is a true structural homologue.
- Q-Cov (Query Coverage): High percentages mean the match covers the
majority of the query protein's overall shape, rather than just a small local motif.
- E-value & Seq Identity: Use these to provide additional evolutionary
context. 8. Perform Functional Analysis: Analyze the text descriptions embedded within the Target ID column for the reported matches.
- Explicitly report the specific protein names/functions of the top
structural homologues.
- Provide a synthesized overview summarizing the entire variety of
different functions, domains, or protein families found across the whole list of homologues (e.g., "Most hits are portal proteins, but there is also a distinct cluster of viral capsid matches..."). 9. Explicitly inform the user of both newly created files (.json and .md) and their locations so they can be seamlessly used in subsequent analysis steps.
* If the API returns an error or the file is missing, inform the user clearly
and ask them to verify the file path.
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Runs Foldseek search for a PDB/mmCIF file against different databases."""
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "scienceskillscommon",
# ]
# [tool.uv.sources]
# scienceskillscommon = { path = "../../scienceskillscommon" }
# ///
import argparse
import json
import os
import sys
import time
import uuid
from science_skills.skills.scienceskillscommon import http_client
ALLOWED_DATABASES = [
"afdb50",
"afdb-swissprot",
"pdb100",
"BFVD",
"mgnify_esm30",
"cath50",
"gmgcl_id",
"bfmd",
"afdb-proteome",
]
MAX_ALIGNMENT_HITS = 300
DEFAULT_EVALUE = 1000
# Respect 0.1 queries per second requirement.
CLIENT = http_client.HttpClient("https://search.foldseek.com", qps=1)
def build_multipart_payload(fields, files):
"""Manually constructs a multipart/form-data byte payload."""
boundary = uuid.uuid4().hex
body = bytearray()
# Add standard form fields (handling lists for multiple databases)
for key, values in fields.items():
if not isinstance(values, list):
values = [values]
for value in values:
body.extend(f"--{boundary}\r\n".encode("utf-8"))
body.extend(
f'Content-Disposition: form-data; name="{key}"\r\n\r\n'.encode(
"utf-8"
)
)
body.extend(f"{value}\r\n".encode("utf-8"))
# Add file data
for key, filepath in files.items():
filename = os.path.basename(filepath)
with open(filepath, "rb") as f:
content = f.read()
body.extend(f"--{boundary}\r\n".encode("utf-8"))
body.extend(
f'Content-Disposition: form-data; name="{key}";'
f' filename="{filename}"\r\n'.encode("utf-8")
)
body.extend(b"Content-Type: application/octet-stream\r\n\r\n")
body.extend(content)
body.extend(b"\r\n")
body.extend(f"--{boundary}--\r\n".encode("utf-8"))
return boundary, body
# ---------------------------------------------------
def main():
# Set up command line argument parsing
parser = argparse.ArgumentParser(
description="Query Foldseek with a PDB/mmCIF file and save the results."
)
parser.add_argument("input_file", help="Path to the mmCIF or PDB file")
parser.add_argument(
"-o",
"--output",
help="Path to save the output JSON file",
default="foldseek_results.json",
)
parser.add_argument(
"--databases",
help="Comma-separated list of databases to search",
default="pdb100,afdb50",
)
args = parser.parse_args()
file_path = args.input_file
output_path = args.output
ticket_url = "https://search.foldseek.com/api/ticket"
# Process and validate the databases
selected_dbs = [db.strip() for db in args.databases.split(",")]
invalid_dbs = [db for db in selected_dbs if db not in ALLOWED_DATABASES]
if invalid_dbs:
print(f"[!] Error: Invalid database(s) provided: {', '.join(invalid_dbs)}")
print(f"[*] Allowed databases are: {', '.join(ALLOWED_DATABASES)}")
sys.exit(1)
# Standard headers for all requests
headers = {
"User-Agent": "",
"Accept": "application/json",
}
print(f"[*] Submitting {file_path} to Foldseek API...")
print(f"[*] Searching databases: {', '.join(selected_dbs)}")
# 1. Submit Ticket
try:
if not os.path.exists(file_path):
raise FileNotFoundError(f"No such file: '{file_path}'")
boundary, body = build_multipart_payload(
fields={"mode": "3diaa", "database[]": selected_dbs},
files={"q": file_path},
)
headers["Content-Type"] = f"multipart/form-data; boundary={boundary}"
response = CLIENT.fetch_json(
ticket_url, method="POST", data=body, headers=headers, timeout=30
)
ticket_id = response["id"]
print(f"[*] Ticket ID generated: {ticket_id}")
except FileNotFoundError as e:
print(f"[!] Error: {e}")
sys.exit(1)
except http_client.HttpError as e:
print(f"[!] API Submission Failed: {e}")
sys.exit(1)
# 2. Poll the server until the job finishes
print("[*] Polling server for completion...")
while True:
status_res = CLIENT.fetch_json(
f"{ticket_url}/{ticket_id}", headers=headers, timeout=20
)
status = status_res.get("status")
if status == "COMPLETE":
print("\n[*] Job marked as COMPLETE.")
break
elif status == "ERROR":
print("\n[!] Foldseek job failed on the server.")
sys.exit(1)
sys.stdout.write(".")
sys.stdout.flush()
time.sleep(10)
time.sleep(3) # Brief pause to allow server-side result finalization.
# 3. Fetch and format the results
print(
"[*] Fetching results (this might take a moment for large databases)...\n"
)
result_url = f"https://search.foldseek.com/api/result/{ticket_id}/0"
res = CLIENT.fetch_json(result_url, headers=headers, timeout=120)
# Save the JSON response to the specified local file
with open(output_path, "w") as json_file:
json.dump(res, json_file, indent=4)
print(f"[*] Raw JSON results successfully saved to: {output_path}\n")
# Output as a Markdown Table for LLM Agent parsing
print("### All Structural Matches")
print("| Target ID | Q-Cov | Prob | E-value | Seq Identity | Aln Length |")
print("|---|---|---|---|---|---|")
alignments_list = []
if isinstance(res, dict):
if "results" in res:
for result_group in res.get("results", []):
for db_alignments in result_group.get("alignments", []):
if isinstance(db_alignments, list):
alignments_list.extend(db_alignments)
elif isinstance(db_alignments, dict):
alignments_list.append(db_alignments)
elif "alignments" in res:
alignments_list = res["alignments"]
elif isinstance(res, list):
alignments_list = res
def get_evalue(hit):
# This field must not be empty as it is used for sorting the results
# If the field is missing or empty, we assign a default value that is
# larger than any reasonable E-value (puts it at the end of the sorted
# list).
try:
return float(
hit.get("eval", hit.get("eValue", hit.get("evalue", DEFAULT_EVALUE)))
)
except ValueError:
return float(DEFAULT_EVALUE)
# Sort all hits first, then take the top MAX_ALIGNMENT_HITS
alignments_list.sort(key=get_evalue)
alignments_list = alignments_list[:MAX_ALIGNMENT_HITS]
for hit in alignments_list:
target = hit.get("target", "N/A")
# --- Corrected Query Coverage Logic ---
q_len = hit.get("qLen", hit.get("qlen", 0))
q_start = hit.get("qStartPos", 0)
q_end = hit.get("qEndPos", 0)
if q_len > 0 and q_end > q_start:
# Calculate the actual number of query residues involved, ignoring gaps
aligned_q_residues = (q_end - q_start) + 1
# Cap at 100.0% to handle any minor index shifting in the API
cov_percentage = min((aligned_q_residues / q_len) * 100, 100.0)
q_cov = f"{cov_percentage:.1f}%"
else:
q_cov = "N/A"
# Extract Probability
prob = hit.get("prob", hit.get("probability", "N/A"))
if isinstance(prob, (float, int)):
prob = f"{prob:.3f}"
else:
prob = str(prob)
evalue = str(hit.get("eval", hit.get("eValue", hit.get("evalue", "N/A"))))
seq_id = str(
hit.get("seqId", hit.get("seqIdentity", hit.get("fident", "N/A")))
)
aln_len = str(
hit.get("alnLength", hit.get("alnLen", hit.get("alnlen", "N/A")))
)
# Print Markdown row without any truncation
print(f"| {target} | {q_cov} | {prob} | {evalue} | {seq_id} | {aln_len} |")
if __name__ == "__main__":
main()
Related skills
FAQ
What file formats does Foldseek Structural Search accept?
Foldseek Structural Search accepts PDB and mmCIF protein structure files as input and runs Foldseek search against configured structural databases from an agent-driven workflow.
What Python version does Foldseek Structural Search require?
Foldseek Structural Search requires Python >=3.10 for its bundled script that executes Foldseek structural similarity searches against protein structure databases.
Is Foldseek Structural Search safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.