MCP Server Builder
anthropics/skills
Guides the design and implementation of Model Context Protocol servers in TypeScript or Python, from tool naming and error messages to evaluation.
“Best Practices for using Coot MCP”
$ npx skills add pemsley/coot --skill coot-best-practices -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install pemsley/coot coot-best-practices --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ 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-srcUse ~/.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/
Install the "coot-best-practices" agent skill from https://github.com/pemsley/coot/tree/main/mcp/docs/skills/best-practices into .claude/skills/coot-best-practices/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "coot-best-practices", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/pemsley/coot/tree/main/mcp/docs/skills/best-practicesType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add pemsley/coot --skill coot-best-practices -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install pemsley/coot coot-best-practices --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/pemsley/coot.git skills-src && mkdir -p .agents/skills && cp -r skills-src/mcp/docs/skills/best-practices .agents/skills/coot-best-practices && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "coot-best-practices" agent skill from https://github.com/pemsley/coot/tree/main/mcp/docs/skills/best-practices into .agents/skills/coot-best-practices/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "coot-best-practices", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add pemsley/coot --skill coot-best-practices -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install pemsley/coot coot-best-practices --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/pemsley/coot.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/mcp/docs/skills/best-practices .cursor/skills/coot-best-practices && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "coot-best-practices" agent skill from https://github.com/pemsley/coot/tree/main/mcp/docs/skills/best-practices into .cursor/skills/coot-best-practices/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "coot-best-practices", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/pemsley/coot.git --path mcp/docs/skills/best-practices--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add pemsley/coot --skill coot-best-practices -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install pemsley/coot coot-best-practices --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/pemsley/coot.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/mcp/docs/skills/best-practices .gemini/skills/coot-best-practices && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "coot-best-practices" agent skill from https://github.com/pemsley/coot/tree/main/mcp/docs/skills/best-practices into .gemini/skills/coot-best-practices/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "coot-best-practices", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install pemsley/coot coot-best-practicesInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add pemsley/coot --skill coot-best-practices -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/pemsley/coot.git skills-src && mkdir -p .github/skills && cp -r skills-src/mcp/docs/skills/best-practices .github/skills/coot-best-practices && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "coot-best-practices" agent skill from https://github.com/pemsley/coot/tree/main/mcp/docs/skills/best-practices into .github/skills/coot-best-practices/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "coot-best-practices", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add pemsley/coot --skill coot-best-practices -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install pemsley/coot coot-best-practices --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/pemsley/coot.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/mcp/docs/skills/best-practices .opencode/skills/coot-best-practices && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "coot-best-practices" agent skill from https://github.com/pemsley/coot/tree/main/mcp/docs/skills/best-practices into .opencode/skills/coot-best-practices/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "coot-best-practices", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
coot-best-practicesCoot Best Practices is a skill in pemsley/coot (168 stars). Its SKILL.md is about 6.1k tokens. Licence: GPL-3.0.
4 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 9648092. It shows what the files ask for, not the result of running them.
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.
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.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
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.
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.
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.
The full file from pemsley/coot at commit 9648092, republished under its GPL-3.0 licence (© pemsley). 1,471 words, ~6,065 tokens.
.claude/skills/coot-best-practices/SKILL.md (or your agent's skills folder).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.
CRITICAL: Understanding run_python() vs run_python_multiline()
Coot MCP provides two tools for executing Python code with different capabilities and limitations:
Use for single expressions that return a value. Cannot handle imports or semicolons.
# ✓ 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)") # FAILSRule: Use run_python() ONLY for:
Use for any code requiring imports, multiple statements, or function definitions:
# ✓ 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:
| Feature | run_python() | run_python_multiline() |
|---|---|---|
| Import statements | ❌ Fails | ✓ Works |
| Semicolons | ❌ Fails | ✓ Works |
| Multiple lines | ❌ Fails | ✓ Works |
| Function defs | ❌ Fails | ✓ Works |
| Simple expressions | ✓ Works | ✓ Works |
| Return value | Returns value | Returns None (use print) |
# ❌ 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)
""")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.
CRITICAL: When writing files from Coot's Python, ALWAYS write to the current directory without specifying a path.
Coot's Python interpreter runs in a specific working directory (/Users/pemsley/Projects/coot/git/coot-main/build/src), which is different from both:
/home/claude)/mnt/user-data/...)Attempting to write to an explicit path that doesn't exist in Coot's context will cause FileNotFoundError.
# ✓ 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()# ✗ 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)# 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)Coot's Python interpreter automatically uses its working directory. By omitting paths:
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.
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.
CRITICAL: On every "Coot Mode" start, before doing anything else:
/mnt/skills/user/coot-essential-api/SKILL.md fileget_function_descriptions() with the complete list of function namesWhy 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:
# 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.
ALWAYS use coot.*_py() functions directly instead of coot_utils.* equivalents when they are simple passthroughs.
coot is auto-imported, coot_utils requires explicit importcoot - The core C++/SWIG binding is automatically availablecoot_utils - Python utility library built on top of cootimport coot_utils before using any of its functionsExample of the problem:
# 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 worksBut in this case (for chain-ids), this is preferred:
coot.get_chain_ids_py(0)_py() suffixclosest_atom_simple_py(), is_valid_model_molecule(), chain_id_py()_py() suffixclosest_atom(), closest_atom_simple(), chain_ids()# 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()# 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()Use coot_utils functions when they provide genuine convenience or abstraction:
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)]# ✅ 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# ✅ 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 wrapperWhen 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.
/mdl/chn/s1.i1-s2.i2/atm[elm]:alocor for residue-name-based selection:
/mdl/chn/*(res)/atm[elm]:alocmdl - Model number (0 or * for any model; /1/ for model 1; // is shorthand for any model)chn - Chain ID (e.g., A, B, X)A,B,C!: !A,B selects all chains except A and B* for all chains (default)s1-s2 - Residue sequence number range:5010-2033.A-120.B (residue 33 ins A through residue 120 ins B)* for all residues (default)(res) - Residue name filter in parentheses(HIS)(ALA,SER,GLY)(!ALA,SER) selects everything except ALA and SER.ic - Insertion code (e.g., .A)atm - Atom name (e.g., CA, N, O)CA,N,O!CA,CB selects all atoms except CA and CB[elm] - Element in square brackets (e.g., [C], [N], [FE])CA[C] selects C-alpha (carbon), not calcium[C,N,O][!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"" (no alt-loc) when atom or element is specifiedAll components are optional and default to * (match everything) when omitted.
Any of the comma-separated list fields (chain, residue name, atom name, element, alt-loc)
can be negated by prefixing with !:
"//!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# 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 chainNote: Selections containing commas must be quoted.
# 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)Single-line expressions return their evaluated value:
coot.is_valid_model_molecule(0) # Returns: 1 or 0Multi-line blocks require explicit return or final expression:
# ❌ 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)]# ✅ CORRECT function name
coot.load_tutorial_model_and_data()
# ❌ INCORRECT function names that don't exist
# coot.tutorial_model_and_data() # Wrong!# 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)}")# ✅ 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}")# ✅ 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# 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
)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:
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.
# ❌ 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)Python wrappers are valuable when they:
chain_ids() calls chain_id_py() in a loop)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()| Task | ❌ Avoid | ✅ Use Instead | Reason |
|---|---|---|---|
| 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 IDs | Multiple C++ calls | coot_utils.chain_ids(imol) | Convenience wrapper adds value |
| Check if valid model | coot_utils.valid_model_molecule_qm() | coot.is_valid_model_molecule(imol) | Direct C++, clearer name |
| Get molecule name | N/A | coot.molecule_name(imol) | Direct C++ only |
| Load tutorial data | coot.tutorial_model_and_data() | coot.load_tutorial_model_and_data() | Correct function name |
# ❌ Will fail with NameError
chains = coot_utils.chain_ids(0)
# ✅ Import first
import coot_utils
chains = coot_utils.chain_ids(0)# ❌ Unnecessary indirection
import coot_utils
atom = coot_utils.closest_atom_simple()
# ✅ Direct and faster
atom = coot.closest_atom_simple_py()# ❌ Function doesn't exist
coot.tutorial_model_and_data()
# ✅ Correct name
coot.load_tutorial_model_and_data()# ✅ 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
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# 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# 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 insteadsearch_coot_functions(pattern) - First choice
list_coot_categories() - For browsing
get_functions_in_category(category) - For comprehensive lists
coot.*_py() functions when they're simple passthroughscoot_utils functions when they add genuine conveniencecoot is auto-imported, coot_utils is notload_tutorial_model_and_data() not tutorial_model_and_data()search_coot_functions with space-separated words for AND logic when searching the APIFollowing 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
Just SKILL.md in mcp/docs/skills/best-practices of pemsley/coot.
Open the folder on GitHubat commit 9648092
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.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Coot Best Practices this skillpemsley/coot | 168 | — | ~6.1k | Automated safety check: Pass | GPL-3.0 | |
| MCP Server Builderanthropics/skills | 180k | 63 repos | ~2.3k | Automated safety check: Pass | Apache-2.0 | |
| MCP Server BuildershareAI-lab/learn-claude-code | 78k | 4 repos | ~1.2k | Automated safety check: Pass | MIT | |
| MemPalace Setup and OperationMemPalace/mempalace | 59k | — | ~2.2k | Automated safety check: Pass | MIT | |
| LangBot Plugin Developmentlangbot-app/LangBot | 18k | — | ~3.9k | Automated safety check: Pass | Apache-2.0 | |
| DocsPrefectHQ/fastmcp | 28k | — | ~1k | Automated safety check: Pass | Apache-2.0 |
anthropics/skills
Guides the design and implementation of Model Context Protocol servers in TypeScript or Python, from tool naming and error messages to evaluation.
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.
MemPalace/mempalace
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.
langbot-app/LangBot
Guides building, debugging and testing LangBot plugins: components, SDK calls, README and locale rules, SDK pitfalls and WebSocket-based testing.
PrefectHQ/fastmcp
Write or revise a page under docs/ for gofastmcp.com. An agent skill from PrefectHQ/fastmcp.
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.
pemsley/coot
Create interactive inline Chart.js graphs directly in the chat from live Coot data.
pemsley/coot
RDKit molecular manipulation and visualization within Coot's Python environment.
pemsley/coot
Best practices for protein structure refinement and validation in Coot.
pemsley/coot
API documentation to be loaded at startup - when starting a Coot session, immediately call getfunctiondescriptions() with the functions listed in this skill.
pemsley/coot
Best practices for creating publication-quality molecular graphics figures in Coot using user-defined colors, ribbons, and molecular representations
pemsley/coot
Best Practices for Model-Building Tools and Refinement. An agent skill from pemsley/coot.
Works with
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.
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.
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.
SKILL.md names no scripts, command-line tools or credentials: Coot Best Practices is instructions for the agent only.
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.
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.
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.
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.
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.
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.