pemsley/coot/best-practices

Claude Code and agent skill by pemsley/coot

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Best Practices for using Coot MCP

Install

npx vskill@latest install pemsley/coot/best-practices

SKILL.md

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

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
160
Repository forks
60
7-day trend score
25

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/pemsley/coot/best-practices/badge)](https://verified-skill.com/skills/pemsley/coot/best-practices)

Something wrong with this skill or its page? Tell us.