Agent skill

Coot Best Practices

by pemsley in pemsley/coot

“Best Practices for using Coot MCP”

— description from SKILL.md by pemsley
GPL-3.0Auto-check passed

Install Coot Best Practices

skills CLI
$ npx skills add pemsley/coot --skill coot-best-practices -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install pemsley/coot coot-best-practices --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/pemsley/coot.git skills-src && mkdir -p .claude/skills && cp -r skills-src/mcp/docs/skills/best-practices .claude/skills/coot-best-practices && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
coot-best-practices
GitHub stars
168
Token cost
~6.1k tokens
SKILL.md length
1,471 words
Files
1
Skills in repo
11
Repo updated
First seen
Licence
GPL-3.0

At a glance

  • Works in 4 steps: Using coot_utils without import → Using coot_utils when unnecessary → Wrong function names → …
  • SKILL.md covers Overview, Python Execution in Coot MCP, File Writing from Coot Python and Startup Procedure, plus 4 more sections
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

About this skill

Coot Best Practices is a skill in pemsley/coot (168 stars). Its SKILL.md is about 6.1k tokens. Licence: GPL-3.0.

Workflow steps

4 steps, taken from the step headings in SKILL.md.

  1. Using coot_utils without import
  2. Using coot_utils when unnecessary
  3. Wrong function names
  4. Getting output from multi-line code

What it can do on your machine

Read from SKILL.md and the folder at commit 9648092. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are python).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Coot Best Practices loads about 6.1k tokens when it runs. Until then it costs about 13 tokens; SKILL.md has 1,471 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~13
When it runs · the whole SKILL.md, loaded when a task matches
~6.1k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from pemsley/coot at commit 9648092, republished under its GPL-3.0 licence (© pemsley). 1,471 words, ~6,065 tokens.

Download SKILL.mdSave it as .claude/skills/coot-best-practices/SKILL.md (or your agent's skills folder).
name
coot-best-practices
description
Best Practices for using Coot MCP

Coot Python API Best Practices

Overview

This skill provides best practices for interacting with Coot's Python API through the MCP server. Following these guidelines ensures optimal performance, correct usage, and reliable results.

Python Execution in Coot MCP

CRITICAL: Understanding run_python() vs run_python_multiline()

Coot MCP provides two tools for executing Python code with different capabilities and limitations:

run_python() - Simple Expressions Only

Use for single expressions that return a value. Cannot handle imports or semicolons.

python
# ✓ CORRECT - single expression, no imports
coot.run_python("1 + 1")                           # Returns: 2
coot.run_python("coot.molecule_name(0)")           # Returns: molecule name
coot.run_python("coot.is_valid_model_molecule(0)") # Returns: 1 or 0

# ✗ WRONG - these will all fail with syntax errors
coot.run_python("import os")                       # FAILS - import not allowed
coot.run_python("import os; os.getcwd()")          # FAILS - semicolon not allowed  
coot.run_python("x = 5; x * 2")                    # FAILS - semicolon not allowed
coot.run_python("import coot_utils; coot_utils.chain_ids(0)")  # FAILS

Rule: Use run_python() ONLY for:

  • Single expressions with no semicolons
  • No import statements
  • Direct function calls that return a value
run_python_multiline() - Everything Else

Use for any code requiring imports, multiple statements, or function definitions:

python
# ✓ CORRECT - use multiline for imports
coot.run_python_multiline("""
import os
result = os.getcwd()
print(result)
""")

# ✓ CORRECT - use multiline for multiple statements
coot.run_python_multiline("""
x = 5
y = x * 2
print(y)
""")

# ✓ CORRECT - use multiline for complex logic
coot.run_python_multiline("""
import coot_utils
chains = coot_utils.chain_ids(0)
for chain in chains:
    print(f"Chain: {chain}")
""")

# ✓ CORRECT - use multiline for function definitions
coot.run_python_multiline("""
def validate_residue(imol, chain, resno):
    info = coot.residue_info_py(imol, chain, resno, "")
    return len(info)

result = validate_residue(0, "A", 42)
print(f"Atoms: {result}")
""")

Rule: Use run_python_multiline() for:

  • Any import statements
  • Multiple statements (even without imports)
  • Function definitions
  • Loops and control flow
  • Anything with complexity
Key Differences Table
Featurerun_python()run_python_multiline()
Import statements❌ Fails✓ Works
Semicolons❌ Fails✓ Works
Multiple lines❌ Fails✓ Works
Function defs❌ Fails✓ Works
Simple expressions✓ Works✓ Works
Return valueReturns valueReturns None (use print)
Common Mistake Pattern
python
# ❌ WRONG - will fail with "invalid syntax"
result = coot.run_python("import coot_utils; coot_utils.chain_ids(0)")

# ✓ CORRECT - use multiline
coot.run_python_multiline("""
import coot_utils
chains = coot_utils.chain_ids(0)
print(chains)
""")
When in Doubt

Use run_python_multiline() - it handles everything run_python() can do, plus more. The only downside is that it returns None rather than a value, so use print() to see output.

File Writing from Coot Python

CRITICAL: When writing files from Coot's Python, ALWAYS write to the current directory without specifying a path.

The Problem

Coot's Python interpreter runs in a specific working directory (/Users/pemsley/Projects/coot/git/coot-main/build/src), which is different from both:

  • The bash working directory (/home/claude)
  • Mounted shared directories (/mnt/user-data/...)

Attempting to write to an explicit path that doesn't exist in Coot's context will cause FileNotFoundError.

✅ CORRECT: Write to Current Directory
python
# ✓ CORRECT - no path, just filename
with open('output.txt', 'w') as f:
    f.write(content)

# ✓ CORRECT - works for any file type
with open('validation.xml', 'w') as f:
    f.write(xml_content)

# ✓ CORRECT - reading also uses just filename
with open('data.txt', 'r') as f:
    content = f.read()
❌ INCORRECT: Don't Specify Paths
python
# ✗ WRONG - will fail with FileNotFoundError
with open('/home/claude/output.txt', 'w') as f:
    f.write(content)

# ✗ WRONG - this path doesn't exist in Coot's context
with open('/mnt/user-data/outputs/file.txt', 'w') as f:
    f.write(content)

# ✗ WRONG - even full paths will fail
import os
output_file = os.path.join('/home/claude', 'output.txt')
with open(output_file, 'w') as f:
    f.write(content)
Pattern for Working with Files
python
# 1. Download/fetch content (if needed)
url = "https://example.com/data.xml"
content = coot.coot_get_url_as_string_py(url)

# 2. Write to current directory (no path!)
with open('data.xml', 'w') as f:
    f.write(content)

# 3. Process the file
import xml.etree.ElementTree as ET
tree = ET.parse('data.xml')  # Read from current directory
root = tree.getroot()

# 4. Write results (again, no path!)
with open('results.txt', 'w') as f:
    f.write(analysis_results)
Why This Works

Coot's Python interpreter automatically uses its working directory. By omitting paths:

  • Files are created in a location that exists
  • No permission issues
  • No cross-filesystem problems
  • Simple and reliable
User Talk

When the user says "here" in an ambiguous way, they typically mean "applying the relevant function to this residue (or atom or chain)" - i.e. the residue (or atom or chain) at the centre of the screen.

Getting Files to the User [warning: this needs review!]

After creating files in Coot's working directory, use bash tools to copy them to /mnt/user-data/outputs where the user can access them, or share results via print output.

Startup Procedure

CRITICAL: On every "Coot Mode" start, before doing anything else:

  1. Read the /mnt/skills/user/coot-essential-api/SKILL.md file
  2. Extract all function names mentioned in that file
  3. Call get_function_descriptions() with the complete list of function names
  4. This loads the essential API documentation into context, providing immediate access to the ~25 core functions needed for typical validation and model-building workflows

Why get_function_descriptions and not search_coot_functions? search_coot_functions can return sparse or incomplete docs for some functions. get_function_descriptions retrieves the full docstring including return value structure. Always prefer get_function_descriptions for known function names — use search_coot_functions only when you don't know the function name yet.

Example:

python
# After reading coot-essential-api/SKILL.md, call:
Coot:get_function_descriptions([
    "set_refinement_immediate_replacement",
    "set_imol_refinement_map",
    "is_valid_model_molecule",
    "is_valid_map_molecule",
    "get_hydrogen_bonds_py",
    # ... all other functions from the essential API
])

Only after completing this startup should you proceed with the user's task.

Critical Rule: Prefer C++ Functions Over Python Wrappers

ALWAYS use coot.*_py() functions directly instead of coot_utils.* equivalents when they are simple passthroughs.

Why?
  1. Performance: C++ functions are significantly faster (no Python overhead)
  2. Import requirements: coot is auto-imported, coot_utils requires explicit import
  3. Simplicity: Direct access to the core API without unnecessary abstraction layers
  4. Reliability: Fewer layers means fewer potential points of failure

Module Import Requirements

Auto-imported
  • coot - The core C++/SWIG binding is automatically available
  • No import statement needed
Requires explicit import
  • coot_utils - Python utility library built on top of coot
  • Must execute: import coot_utils before using any of its functions

Example of the problem:

python
# This will fail with NameError if coot_utils not imported
coot_utils.chain_ids(0)

# Solution:
import coot_utils
coot_utils.chain_ids(0)  # Now works

But in this case (for chain-ids), this is preferred:

python
coot.get_chain_ids_py(0)

Function Naming Conventions

C++ Functions (SWIG bindings)
  • End with _py() suffix
  • Examples: closest_atom_simple_py(), is_valid_model_molecule(), chain_id_py()
  • These are the core functions - prefer these
Python Wrapper Functions
  • No _py() suffix
  • Examples: closest_atom(), closest_atom_simple(), chain_ids()
  • Only use when they provide genuine convenience

Specific Function Guidance

✅ CORRECT: Getting Closest Atom
python
# Get closest atom across all displayed molecules
atom_spec = coot.closest_atom_simple_py()
# Returns: [imol, chain-id, resno, ins-code, atom-name, alt-conf, [x, y, z]]

# Get closest atom in specific molecule
atom_spec = coot.closest_atom_py(0)
# Returns: [imol, chain-id, resno, ins-code, atom-name, alt-conf, [x, y, z]]

# Get raw closest atom (no CA substitution)
atom_spec = coot.closest_atom_raw_py()
❌ INCORRECT: Don't use coot_utils for simple passthroughs
python
# DON'T DO THIS - unnecessary import and no added value
import coot_utils
atom_spec = coot_utils.closest_atom(0)  # Just calls coot.closest_atom_py()

# DON'T DO THIS EITHER
atom_spec = coot_utils.closest_atom_simple()  # Just calls coot.closest_atom_simple_py()
✅ CORRECT: When to use coot_utils

Use coot_utils functions when they provide genuine convenience or abstraction:

python
import coot_utils

# chain_ids() is a convenience wrapper that constructs a list
# It calls coot.chain_id_py() in a loop and builds a list
chains = coot_utils.chain_ids(0)  # Returns: ['A', 'B']

# Without coot_utils, you'd have to do:
n = coot.n_chains(0)
chains = [coot.chain_id_py(0, i) for i in range(n)]
Checking Molecule Validity
python
# ✅ CORRECT: Direct C++ function
if coot.is_valid_model_molecule(0):
    print("Molecule 0 is a valid model")

if coot.is_valid_map_molecule(1):
    print("Molecule 1 is a valid map")

# ❌ INCORRECT: Don't use coot_utils for this
import coot_utils
if coot_utils.valid_model_molecule_qm(0):  # Unnecessary
    pass
Getting Molecule Information
python
# ✅ CORRECT: Direct access
name = coot.molecule_name(0)
n_chains = coot.n_chains(0)

# ✅ CORRECT: When coot_utils adds value
import coot_utils
chains = coot_utils.chain_ids(0)  # Convenience wrapper

MMDB Atom Selection Syntax

When using functions like new_molecule_by_atom_selection(), superpose_with_atom_selection(), or get_hydrogen_bonds_py(), use MMDB CID (Coordinate ID) strings to specify which atoms to include.

Format
/mdl/chn/s1.i1-s2.i2/atm[elm]:aloc

or for residue-name-based selection:

/mdl/chn/*(res)/atm[elm]:aloc
Components
  • mdl - Model number (0 or * for any model; /1/ for model 1; // is shorthand for any model)
  • chn - Chain ID (e.g., A, B, X)
    • Comma-separated list: A,B,C
    • Negation with !: !A,B selects all chains except A and B
    • * for all chains (default)
  • s1-s2 - Residue sequence number range:
    • Single: 50
    • Range: 10-20
    • With insertion codes: 33.A-120.B (residue 33 ins A through residue 120 ins B)
    • * for all residues (default)
  • (res) - Residue name filter in parentheses
    • Single: (HIS)
    • Comma-separated list: (ALA,SER,GLY)
    • Negation: (!ALA,SER) selects everything except ALA and SER
  • .ic - Insertion code (e.g., .A)
  • atm - Atom name (e.g., CA, N, O)
    • Comma-separated list: CA,N,O
    • Negation: !CA,CB selects all atoms except CA and CB
  • [elm] - Element in square brackets (e.g., [C], [N], [FE])
    • Useful for disambiguation: CA[C] selects C-alpha (carbon), not calcium
    • Comma-separated list: [C,N,O]
    • Negation: [!H] selects all non-hydrogen atoms
  • :aloc - Alternate location indicator
    • :A selects alt-loc A
    • :,A selects atoms with no alt-loc or alt-loc A
    • Defaults to "" (no alt-loc) when atom or element is specified

All components are optional and default to * (match everything) when omitted.

Show full SKILL.md (583 more words)Show less
Negation

Any of the comma-separated list fields (chain, residue name, atom name, element, alt-loc) can be negated by prefixing with !:

python
"//!A"                         # All chains except A
"//A/(!GLY,ALA)"               # Non-GLY, non-ALA residues in chain A
"//A/*/!CA,CB"                 # All atoms except CA and CB in chain A
"//A/*/[!H]"                   # All non-hydrogen atoms in chain A
Examples
python
# Select entire chain
"//A"                          # All atoms in chain A
"//A,B,C"                      # All atoms in chains A, B, and C

# Select residue range
"//A/12-130"                   # Residues 12-130 in chain A
"//A/12-130/CA"                # CA atoms from residues 12-130 in chain A
"//A/33.A-120.B"               # Residue range with insertion codes

# Select by residue type
"//B/10-20(GLY)"               # GLY residues 10-20 in chain B
"//A/*(HIS)"                   # All HIS residues in chain A
"//A/(GLU,ASP)"                # All glutamate and aspartate in chain A
"//A/(!ALA,GLY)"               # All residues except ALA and GLY in chain A

# Select specific atom
"//A/50/CA"                    # CA atom of residue 50 in chain A
"//A/50(HIS)/CA"               # CA atom of HIS 50 in chain A

# Element disambiguation
"CA[C]"                        # C-alpha atoms (carbon), not calcium
"//A/*/[C]"                    # All carbon atoms in chain A
"//A/*/[!H]"                   # All non-hydrogen atoms in chain A

# Alt-loc selection
"//A/50/CA:A"                  # CA in alt-loc A
"[C]:,A"                       # Carbons with no alt-loc or alt-loc A

# Wildcards
"*"                            # All atoms in the structure
"/1"                           # All atoms in model 1
"33-120"                       # Residues 33-120 in any chain

Note: Selections containing commas must be quoted.

Usage Examples
python
# Create a new molecule with chains A and B
imol_ab = coot.new_molecule_by_atom_selection(0, "//A,B")

# Create a new molecule with CA atoms from residues 10-50 in chain A
imol_ca = coot.new_molecule_by_atom_selection(0, "//A/10-50/CA")

# Superpose using CA atoms
coot.superpose_with_atom_selection(
    imol1=0,
    imol2=1,
    mmdb_atom_sel_str_1="//A/10-100/CA",
    mmdb_atom_sel_str_2="//A/10-100/CA",
    move_imol2_copy_flag=0
)

# Hydrogen bonds between a residue and a chain
coot.get_hydrogen_bonds_py(0, "//A/35", "//A", 0)

Code Execution Patterns

Single-line expressions

Single-line expressions return their evaluated value:

python
coot.is_valid_model_molecule(0)  # Returns: 1 or 0
Multi-line code blocks

Multi-line blocks require explicit return or final expression:

python
# ❌ This returns None (print doesn't return a value)
mols = []
for i in range(3):
    mols.append(i)
print(mols)

# ✅ CORRECT: Return the value or use final expression
mols = []
for i in range(3):
    mols.append(i)
mols  # Final expression is returned

# ✅ ALSO CORRECT: List comprehension (single expression)
[i for i in range(3)]

Common Tasks Reference

Loading Tutorial Data
python
# ✅ CORRECT function name
coot.load_tutorial_model_and_data()

# ❌ INCORRECT function names that don't exist
# coot.tutorial_model_and_data()  # Wrong!
Listing Molecules
python
# Check molecules 0-5
[(i, coot.is_valid_model_molecule(i), coot.is_valid_map_molecule(i)) for i in range(6)]

# Get molecule names for valid molecules
for i in range(10):
    if coot.is_valid_model_molecule(i):
        print(f"Model {i}: {coot.molecule_name(i)}")
    elif coot.is_valid_map_molecule(i):
        print(f"Map {i}: {coot.molecule_name(i)}")
Working with Chain IDs
python
# ✅ CORRECT: Use coot_utils for convenience
import coot_utils
chains = coot_utils.chain_ids(0)  # Returns: ['A', 'B', 'C']

# Iterate over chains
for chain in chains:
    print(f"Chain {chain}")
Getting Active/Closest Residue
python
# ✅ Get closest atom across displayed molecules
atom = coot.closest_atom_simple_py()
if atom:
    imol, chain, resno, ins, atom_name, alt, coords = atom[0], atom[1], atom[2], atom[3], atom[4], atom[5], atom[6]

# ✅ Get active residue (with potential CA substitution)
import coot_utils
active = coot_utils.active_residue()
if active:
    imol, chain, resno, ins, atom_name, alt = active

Density Fit Analysis

Map Correlation Functions
python
# Get correlation for specific residues
import coot_utils

# Single residue
residue_spec = ["A", 42, ""]
correlation = coot.density_score_residue_py(0, residue_spec, 1)

# Per-residue correlation for a range
residue_specs = [["A", i, ""] for i in range(40, 50)]
results = coot.map_to_model_correlation_per_residue_py(0, residue_specs, 0, 1)
# Returns: [(residue_spec, correlation), ...]

# Main function for "which residue fits worst?"
stats = coot.map_to_model_correlation_stats_per_residue_range_py(
    0, "A", 1, 100, 1  # imol, chain, start, end, imol_map
)

Zoom and View Settings

When adjusting the view in Coot, remember that higher zoom values mean the molecule appears larger on screen (i.e., zoomed in), while lower values show more of the scene (zoomed out). Typical ranges:

  • 150-300: Whole-molecule overview (appropriate for ribbons, surfaces, overall architecture)
  • 50-100: Domain or region level
  • 20-50: Residue-level detail (inspecting side chains, density fit, rotamers)

Small proteins like RNase A (~124 residues) may appear compact even at zoom 200, while larger complexes will fill the screen at lower zoom values. When presenting a ribbon diagram or other overview representation, consider turning off the bond representation (coot.set_mol_displayed(imol, 0)) and hiding electron density maps (coot.set_map_displayed(imol_map, 0)) to reduce visual clutter while keeping the ribbon mesh visible.

Use coot.zoom_factor() to query the current zoom level and coot.set_zoom(value) to set it. For interactive exploration, users can also adjust zoom with the scroll wheel.

Performance Considerations

Function Call Overhead
python
# ❌ SLOW: Multiple function calls through Python wrapper
import coot_utils
for i in range(1000):
    atom = coot_utils.closest_atom(0)  # Unnecessary indirection

# ✅ FAST: Direct C++ calls
for i in range(1000):
    atom = coot.closest_atom_py(0)
When Python Wrappers Are Worth It

Python wrappers are valuable when they:

  1. Aggregate multiple C++ calls (e.g., chain_ids() calls chain_id_py() in a loop)
  2. Transform data into more convenient formats
  3. Provide meaningful abstractions that simplify complex operations
  4. Add error handling or validation logic

Decision Tree

Need to call a Coot function?
│
├─ Does it require coot_utils for convenience features?
│  └─ YES → import coot_utils and use it
│     Examples: chain_ids(), active_residue()
│
└─ NO → Use coot.*_py() directly
   Examples: closest_atom_simple_py(), is_valid_model_molecule()

Quick Reference Table

Task❌ Avoid✅ Use InsteadReason
Get closest atom (all molecules)coot_utils.closest_atom_simple()coot.closest_atom_simple_py()Direct C++, no import needed
Get closest atom (specific mol)coot_utils.closest_atom(imol)coot.closest_atom_py(imol)Direct C++, no import needed
Get chain IDsMultiple C++ callscoot_utils.chain_ids(imol)Convenience wrapper adds value
Check if valid modelcoot_utils.valid_model_molecule_qm()coot.is_valid_model_molecule(imol)Direct C++, clearer name
Get molecule nameN/Acoot.molecule_name(imol)Direct C++ only
Load tutorial datacoot.tutorial_model_and_data()coot.load_tutorial_model_and_data()Correct function name

Common Mistakes to Avoid

1. Using coot_utils without import
python
# ❌ Will fail with NameError
chains = coot_utils.chain_ids(0)

# ✅ Import first
import coot_utils
chains = coot_utils.chain_ids(0)
2. Using coot_utils when unnecessary
python
# ❌ Unnecessary indirection
import coot_utils
atom = coot_utils.closest_atom_simple()

# ✅ Direct and faster
atom = coot.closest_atom_simple_py()
3. Wrong function names
python
# ❌ Function doesn't exist
coot.tutorial_model_and_data()

# ✅ Correct name
coot.load_tutorial_model_and_data()
4. Getting output from multi-line code
python
# ✅ Use print() to see output - it appears in stdout
result = []
for i in range(5):
    result.append(i)
print(result)  # Output: [0, 1, 2, 3, 4]

# ❌ A bare expression at the end of multi-line code does NOT return a value
result = []
for i in range(5):
    result.append(i)
result  # Returns None - this doesn't work!

## API Discovery Tools

### Using search_coot_functions

The `search_coot_functions` tool is your primary method for finding Coot functions. It supports powerful search patterns:

**Space-separated words = Logical AND**
```python
# Find functions containing ALL these words
search_coot_functions("map model correlation")
# Returns functions like: map_to_model_correlation_stats_per_residue_range_py

search_coot_functions("residue range chain")
# Returns functions dealing with residue ranges in chains

search_coot_functions("min max residue")
# Returns functions with all three words (not just any one)

Single words = Simple search

python
search_coot_functions("correlation")  # All functions with "correlation"
search_coot_functions("validation")   # All functions with "validation"
search_coot_functions("rotamer")      # All functions with "rotamer"

Common search patterns:

  • "map correlation" - density fit functions
  • "residue validation" - geometry checking
  • "chain residue" - chain/residue operations
  • "ligand environment" - ligand analysis
  • "ramachandran" - backbone validation
  • "density fit" - map fitting functions
✅ CORRECT Search Strategy
python
# Looking for functions to get residues in a chain
search_coot_functions("chain residue")  # Logical AND

# Looking for min/max residue number functions
search_coot_functions("min max residue")  # All three words required

# Looking for correlation analysis
search_coot_functions("correlation residue")  # Both words required
❌ INCORRECT Search Strategy
python
# DON'T use grep with pipe (|) when you mean AND
# This searches for min OR max OR residue (logical OR)
# Use search_coot_functions with spaces instead
When to use each discovery tool
  1. search_coot_functions(pattern) - First choice

    • Use space-separated words for AND logic
    • Returns max 40 results with documentation
    • Best for targeted searches
  2. list_coot_categories() - For browsing

    • Returns: ['load', 'read', 'display', 'refinement', 'validation', 'ligand', 'util']
    • Use when you want to explore a general area
  3. get_functions_in_category(category) - For comprehensive lists

    • Returns all functions in a category (50-200 functions)
    • Use after identifying the right category
Search Tips
  • Start with 2-3 specific words that describe what you need
  • If too many results, add more words to narrow down
  • If no results, try synonyms or broader terms
  • Common terms: validation, correlation, residue, chain, map, model, ligand, fit, geometry

Summary

  1. Always prefer coot.*_py() functions when they're simple passthroughs
  2. Only use coot_utils functions when they add genuine convenience
  3. Remember coot is auto-imported, coot_utils is not
  4. Use single-line expressions when possible for cleaner returns
  5. Check function names - load_tutorial_model_and_data() not tutorial_model_and_data()
  6. Use search_coot_functions with space-separated words for AND logic when searching the API

Following these practices ensures optimal performance and correct API usage when working with Coot through the MCP server.

© pemsley, GPL-3.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in mcp/docs/skills/best-practices of pemsley/coot.

Open the folder on GitHubat commit 9648092

Compare with similar skills

Coot Best Practices next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Coot Best Practices compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Coot Best Practices this skillpemsley/coot168—~6.1kAutomated safety check: PassGPL-3.0
MCP Server Builderanthropics/skills180k63 repos~2.3kAutomated safety check: PassApache-2.0
MCP Server BuildershareAI-lab/learn-claude-code78k4 repos~1.2kAutomated safety check: PassMIT
MemPalace Setup and OperationMemPalace/mempalace59k—~2.2kAutomated safety check: PassMIT
LangBot Plugin Developmentlangbot-app/LangBot18k—~3.9kAutomated safety check: PassApache-2.0
DocsPrefectHQ/fastmcp28k—~1kAutomated safety check: PassApache-2.0

Similar skills

  • MCP Server Builder

    anthropics/skills

    Official

    Guides the design and implementation of Model Context Protocol servers in TypeScript or Python, from tool naming and error messages to evaluation.

    180k GitHub starsUsed in 63 repos~2.3k tokens
    Agent WorkflowsAuto-check passed
  • MCP Server Builder

    shareAI-lab/learn-claude-code

    Walks through building MCP servers in Python or TypeScript that expose tools, resources and prompts to Claude, with templates, registration and testing.

    78k GitHub starsUsed in 4 repos~1.2k tokens
    Agent WorkflowsAuto-check passed
  • Installs and configures MemPalace as a private local palace, a shared-brain hub or a client of an existing hub, including MCP registration and version-correct initialization.

    59k GitHub stars~2.2k tokensUpdated today
    Agent WorkflowsAuto-check passed
  • LangBot Plugin Development

    langbot-app/LangBot

    Guides building, debugging and testing LangBot plugins: components, SDK calls, README and locale rules, SDK pitfalls and WebSocket-based testing.

    18k GitHub stars~3.9k tokensUpdated today
    DevelopmentAuto-check passed
  • Docs

    PrefectHQ/fastmcp

    Write or revise a page under docs/ for gofastmcp.com. An agent skill from PrefectHQ/fastmcp.

    28k GitHub stars~1k tokensUpdated today
    Frontend & DesignAuto-check passed
  • VectCutAPI Video Editing

    sun-guannan/VectCutAPI

    Drives CapCut or JianYing through an HTTP and MCP API: create drafts, add video, audio, text, subtitles and effects, preview on the web and batch-produce videos.

    2.3k GitHub stars~2.1k tokensUpdated 7 days ago
    Media & CreativeAuto-check passed

More from pemsley/coot

All 11 skills in this repo
  • Coot Inline Graphs

    pemsley/coot

    Create interactive inline Chart.js graphs directly in the chat from live Coot data.

    168 GitHub stars~2.8k tokensUpdated 2 days ago
    Auto-check passed
  • Coot Rdkit

    pemsley/coot

    RDKit molecular manipulation and visualization within Coot's Python environment.

    168 GitHub stars~981 tokensUpdated 2 days ago
    Auto-check passed
  • Coot Refinement

    pemsley/coot

    Best practices for protein structure refinement and validation in Coot.

    168 GitHub stars~2k tokensUpdated 2 days ago
    Auto-check passed
  • Coot Essential API

    pemsley/coot

    API documentation to be loaded at startup - when starting a Coot session, immediately call getfunctiondescriptions() with the functions listed in this skill.

    168 GitHub stars~4.5k tokensUpdated 2 days ago
    Auto-check passed
  • Coot Figure Making

    pemsley/coot

    Best practices for creating publication-quality molecular graphics figures in Coot using user-defined colors, ribbons, and molecular representations

    168 GitHub stars~7.3k tokensUpdated 2 days ago
    Auto-check passed
  • Best Practices for Model-Building Tools and Refinement. An agent skill from pemsley/coot.

    168 GitHub stars~13k tokensUpdated 2 days ago
    Auto-check passed

Questions about Coot Best Practices

How do I install Coot Best Practices in Claude Code?

Run `npx skills add pemsley/coot --skill coot-best-practices -a claude-code`. Or copy the skill folder (mcp/docs/skills/best-practices in pemsley/coot) into .claude/skills/coot-best-practices in your project. Claude Code loads it when a task matches its description.

How do I install Coot Best Practices in Codex?

Run `npx skills add pemsley/coot --skill coot-best-practices -a codex`. Or copy the skill folder (mcp/docs/skills/best-practices in pemsley/coot) into .agents/skills/coot-best-practices in your project. Codex loads it when a task matches its description.

Can I use Coot Best Practices in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add pemsley/coot --skill coot-best-practices -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/coot-best-practices, .gemini/skills/coot-best-practices, .github/skills/coot-best-practices and .opencode/skills/coot-best-practices in your project.

What does Coot Best Practices need to run?

SKILL.md names no scripts, command-line tools or credentials: Coot Best Practices is instructions for the agent only.

Does Coot Best Practices access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Coot Best Practices safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Coot Best Practices use?

Coot Best Practices is published under the GPL-3.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Coot Best Practices use?

About 6.1k tokens (SKILL.md is roughly 24k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Coot Best Practices?

Skills that share tags, products or a category with Coot Best Practices: MCP Server Builder (anthropics/skills, 180k stars), MCP Server Builder (shareAI-lab/learn-claude-code, 78k stars), MemPalace Setup and Operation (MemPalace/mempalace, 59k stars) and LangBot Plugin Development (langbot-app/LangBot, 18k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Coot Best Practices?

pemsley (a GitHub user) maintains it in pemsley/coot, which has 168 GitHub stars. The repository holds 11 skills in this directory. The repository was last updated on October 8, 2026.

Source: pemsley/coot on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.