Generate Nanome workspaces and scenes with molecular structures from RCSB PDB, AlphaFold, PubChem, ChEMBL, SWISS-MODEL, COD, and local files. Use this skill whenever the user wants to create a Nanome workspace, set up molecular visualization sessions, load structures into Nanome, organize molecular data into scenes, or describe a drug discovery/biochemistry session in Nanome. Trigger on any mention of Nanome + workspaces, scenes, molecules, PDB IDs, RCSB structures, or molecular visualization setup.
This skill creates NEW Nanome workspaces populated with molecular structures, organized into scenes with custom visualization components. It is the "on-ramp" for Nanome — get a workspace up and running quickly with the right structures, sensible scene organization, and useful default views. Once created, hand off to MARA (the in-session AI assistant) for interactive refinement, selection-based edits, and live exploration.
This skill does NOT edit existing workspaces. It generates fresh workspaces from scratch based on user intent.
The user triggering this skill IS the go-ahead. Do NOT ask for confirmation — resolve, build, execute, and deliver the workspace URL. Follow these four steps without interruption.
Parse what the user wants to visualize and why. Infer aggressively:
Decision point — ask vs infer:
Immediately resolve identifiers to concrete database sources and fetch metadata (chain count, ligands, resolution) to inform scene design.
Create 1 scene with appropriate visualization components. Only create multiple scenes if the user explicitly asks for them (e.g., "create separate scenes for binding site and surface view"). Do not present for approval — proceed directly to script generation.
Choose components based on structure type:
Decision point — add-default vs custom components:
add-default when the user has no specific visualization preferences and the structure type has clear defaults.Detect the execution environment and use the appropriate mode:
nanome_* MCP tools available → MCP Mode — call MCP tools sequentially (see MCP Mode below)Detection is implicit — check which tools are in your tool list. Do not ask the user which mode to use.
After execution completes (or after generating the artifact), present:
https://mara.nanome.ai/workspaces/{id}Artifact Mode note: The workspace URL is shown immediately in the artifact UI after the user clicks "Build Workspace". In your text response, explain that the artifact will create the workspace when they click the button.
Auth must NEVER block workspace creation. Build the workspace first; handle auth issues after if they arise.
Authentication credentials are stored in ~/.nanome/config.json:
{
"api_base": "https://workspaces.nanome.ai",
"token": "your-nanome-auth-token"
}
~/.nanome/config.json for api_base and token.nanome-auth skill to authenticate the user. This opens their browser to mara.nanome.ai/settings where they create an API key and paste it back. After auth completes, proceed with workspace creation.nanome-auth skill to re-authenticate, then retry the failed operation.Every generated script must include this auth setup at the top:
# Load auth config
CONFIG_FILE="$HOME/.nanome/config.json"
API_BASE="https://workspaces.nanome.ai"
TOKEN=""
if [ -f "$CONFIG_FILE" ]; then
API_BASE=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('api_base', '$API_BASE'))" 2>/dev/null || echo "$API_BASE")
TOKEN=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('token', ''))" 2>/dev/null || echo "")
fi
AUTH_ARGS=()
if [ -n "$TOKEN" ]; then
AUTH_ARGS=(-H "Authorization: Bearer $TOKEN")
fi
All curl commands in the script must include "${AUTH_ARGS[@]}" to conditionally pass auth:
curl -s "${AUTH_ARGS[@]}" -X POST "$API_BASE/workspaces" ...
https://workspaces.nanome.ai — all REST API calls go herehttps://mara.nanome.ai/workspaces/{id} — this is the link you give to usershttps://mara.nanome.ai/login — for browser-based authhttps://mara.nanome.ai/info — returns workspace_api_url dynamically| Source | Identifier Format | Download URL | Output Format |
|---|---|---|---|
| RCSB PDB | 4-char code (e.g., 1HVR) | https://files.rcsb.org/download/{ID}.pdb | PDB |
| RCSB PDB (large) | 4-char code | https://files.rcsb.org/download/{ID}.cif | CIF |
| AlphaFold DB | UniProt ID (e.g., P00533) | https://alphafold.ebi.ac.uk/files/AF-{ID}-F1-model_v4.pdb | PDB (validate v4 via HEAD) |
| PubChem | CID (e.g., 2244) | https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/CID/{ID}/SDF | SDF |
| ChEMBL | ChEMBL ID (e.g., CHEMBL25) | https://www.ebi.ac.uk/chembl/api/data/molecule/{ID}.sdf | SDF |
| SWISS-MODEL | UniProt ID | https://swissmodel.expasy.org/repository/uniprot/{ID}.pdb | PDB |
| COD | COD ID (e.g., 1000000) | https://www.crystallography.net/cod/{ID}.cif | CIF |
| Local files | File path | Direct upload | PDB/SDF/CIF/PQR |
When the user provides an identifier or description, apply these pattern-matching rules in order:
1HVR, 6LU7) — treat as RCSB PDB ID. Use CIF instead of PDB when the structure has more than 62 chains (PDB format limitation).[OPQ][0-9][A-Z0-9]{3}[0-9] or [A-NR-Z][0-9]([A-Z][A-Z0-9]{2}[0-9]){1,2} (e.g., P00533, Q9Y6K9) — route to AlphaFold DB first. Fall back to SWISS-MODEL if AlphaFold has no prediction.CID 2244, or user says "aspirin, CID 2244") — PubChem.CHEMBL25, CHEMBL941) — ChEMBL.COD 1000000) — COD./, ./, ~, or contains common extensions .pdb, .sdf, .cif, .pqr) — local file upload.https://search.rcsb.org/rcsbsearch/v2/query with a text search query.https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/{NAME}/cids/JSONBefore designing scenes, fetch metadata to understand structure contents:
RCSB PDB:
curl -s "https://data.rcsb.org/rest/v1/core/entry/{ID}"
Key fields: rcsb_entry_info.resolution_combined, rcsb_entry_info.polymer_entity_count, struct.title, entity descriptions, ligand identifiers.
PubChem:
curl -s "https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/CID/{ID}/property/MolecularFormula,MolecularWeight,IUPACName/JSON"
Key fields: MolecularFormula, MolecularWeight, IUPACName.
AlphaFold:
curl -s -I "https://alphafold.ebi.ac.uk/files/AF-{ID}-F1-model_v4.pdb"
A 200 response confirms the prediction exists at version 4. If not, try earlier versions or fall back to SWISS-MODEL.
Include this function in every generated script to handle downloads with error checking:
download_structure() {
local url="$1" output="$2"
local http_code
http_code=$(curl -s -o "$output" -w "%{http_code}" "$url")
if [ "$http_code" != "200" ] || [ ! -s "$output" ]; then
echo "ERROR: Failed to download $url (HTTP $http_code)" >&2
rm -f "$output"
return 1
fi
echo "Downloaded: $output"
}
Use this for every structure download in the script:
download_structure "https://files.rcsb.org/download/1HVR.pdb" "$WORKDIR/1HVR.pdb" || exit 1
Components define how atoms from a structure are filtered and visualized within a scene. Each component is a JSON object sent individually via:
POST /workspaces/{id}/scenes/{serial}/components/add
One component per call — do NOT send an array. Each call adds a single component.
{
"name": "Protein",
"filters": [
{ "kind": "EntrySerial", "entrySerial": 1 },
{ "kind": "CurrentModel" },
{ "kind": "Substructure", "substructureType": "Protein" }
],
"visualizations": [
{
"kind": "Ribbon",
"visible": true,
"ribbonStyle": "Cartoon",
"size": { "sizeOption": "Uniform", "scale": 1.0 },
"coloring": {
"kind": "Property",
"carbonsOnly": false,
"propertySource": "ChainInstance"
}
}
]
}
visible (boolean, required) — present on every visualization object. Default true.size (object, required on ALL visualization types) — { "sizeOption": "NoSize"|"Uniform"|"Physical"|"Disorder", "scale": float }. Omitting size causes Internal Server Error. For Ribbon and MolecularSurface, use { "sizeOption": "Uniform", "scale": 1.0 } as default.coloring is nested inside each visualization, NOT a peer field on the component. Every visualization has its own coloring object.carbonsOnly (boolean, required) — present on every coloring object. Default false. When true, only carbon atoms use the specified coloring scheme; other elements use element-type coloring.ComponentInput does not have a hidden field. To hide a component after creation, use a separate call: POST /workspaces/{id}/scenes/{serial}/components/toggle-hidden with body { "componentSerials": [N], "hidden": true }. Use this for solvent/ion components that should be hidden by default.Filters narrow which atoms a component targets. The filters array on a component applies all filters together (intersection by default).
EntrySerial — select atoms from a specific loaded structure by its entry serial number:
{ "kind": "EntrySerial", "entrySerial": 1 }
CurrentModel — select atoms from the currently active model (for multi-model structures like NMR ensembles):
{ "kind": "CurrentModel" }
ModelSerials — select specific models by serial number:
{ "kind": "ModelSerials", "serials": [1, 2, 3, 5] }
ChainNames — select specific chains by name:
{ "kind": "ChainNames", "names": ["A", "B"] }
ResidueNames — select residues by 3-letter code:
{ "kind": "ResidueNames", "names": ["ALA", "TRP"] }
ResidueSerials — select residues by serial number:
{ "kind": "ResidueSerials", "serials": [1, 2, 3, 100, 150] }
AtomNames — select atoms by name:
{ "kind": "AtomNames", "names": ["CA", "N", "O"] }
EnumeratedAtoms — select atoms by serial number:
{ "kind": "EnumeratedAtoms", "serials": [1, 2, 3, 10, 50] }
ConnectedResidue — select residues connected to the current selection:
{ "kind": "ConnectedResidue" }
Substructure — select atoms by macromolecular substructure type:
{ "kind": "Substructure", "substructureType": "Protein" }
Valid substructureType values: Protein, DNA, RNA, PNA, Ligand, Saccharide, Ion, Solvent, Nucleic, Polymer.
Distance — select atoms within a distance (angstroms) of atoms matching containedFilters:
{
"kind": "Distance",
"distance": 5.0,
"wholeResidue": true,
"ignoreSelf": true,
"containedFilters": [
{ "kind": "Substructure", "substructureType": "Ligand" }
]
}
distance — radius in angstroms.wholeResidue — if true, include the entire residue when any atom is within distance.ignoreSelf — if true, exclude the atoms that define the distance center.containedFilters — array of filters defining the center atoms.Union — atoms matching ANY child filter:
{
"kind": "Union",
"containedFilters": [
{ "kind": "Substructure", "substructureType": "Ligand" },
{ "kind": "Substructure", "substructureType": "Ion" }
]
}
Intersect — atoms matching ALL child filters:
{
"kind": "Intersect",
"containedFilters": [
{ "kind": "ChainNames", "names": ["A"] },
{ "kind": "ResidueSerials", "serials": { "ranges": ["50-100"], "sets": [] } }
]
}
Except — exclude atoms matching child filters:
{
"kind": "Except",
"containedFilters": [
{ "kind": "Substructure", "substructureType": "Solvent" }
]
}
Binding site residues within 5A of a ligand:
{
"kind": "Intersect",
"containedFilters": [
{ "kind": "EntrySerial", "entrySerial": 1 },
{ "kind": "CurrentModel" },
{
"kind": "Distance",
"distance": 5.0,
"wholeResidue": true,
"ignoreSelf": true,
"containedFilters": [
{ "kind": "Substructure", "substructureType": "Ligand" }
]
}
]
}
Each entry in the visualizations array on a component defines a rendering style. All visualizations require visible and coloring.
| Kind | Required Properties |
|---|---|
| Atomistic | visible, size, coloring, renderAtom (All/NonBondedOnly/None), renderBond (bool), bondScale (float). Optional: renderHydrogen (None/PolarOnly/All) |
| MolecularSurface | visible, size, coloring, opacity (0-1), subsurface (bool). Optional: wireframe (bool) |
| Ribbon | visible, size, coloring, ribbonStyle (Cartoon/Coil) |
| ResidueLabel | visible, size, coloring |
| Interaction | visible, size, coloring, interactionType, bondScale (float), gapScale (float) |
IMPORTANT: size is required on EVERY visualization type. Omitting it causes HTTP 500. Use {"sizeOption":"Uniform","scale":1.0} as the default for Ribbon, MolecularSurface, and ResidueLabel.
Coloring is always nested inside each visualization object. Every coloring object requires carbonsOnly.
| Kind | Required Properties |
|---|---|
| ElementType | carbonsOnly |
| Property | carbonsOnly, propertySource (BFactor/Occupancy/ChainName/ChainInstance/AtomSerial/AtomName/ModelInstance). Optional: paletteType (Default/Interpolated) + colors (hex array) |
| ResidueType | carbonsOnly |
| SecondaryStructure | carbonsOnly |
| Uniform | carbonsOnly, uniformColor (hex string e.g. "00FF00") |
| Enumerated | carbonsOnly, colors (array of hex strings) |
| HydrogenBonding | carbonsOnly |
| Electrostatic | carbonsOnly |
Use these EXACT API enum values — they differ from common shorthand:
| Interaction | API Enum Value |
|---|---|
| Hydrogen bonds | HydrogenBonds |
| Hydrogen bonds (polymer-polymer) | HydrogenBondsPolymerPolymer |
| Hydrophobic | Hydrophobic |
| Salt bridges | SaltBridges |
| Pi-cation | PiCation |
| Pi stacking | PiStacking |
| Halogen | Halogen |
| Metal coordination | MetalCoordination |
| Clash | Clash |
Copy-paste-ready visualization JSON objects for common representation styles.
{ "kind": "Atomistic", "visible": true, "size": { "sizeOption": "Physical", "scale": 0.25 }, "coloring": { "kind": "Property", "carbonsOnly": true, "propertySource": "ChainInstance" }, "renderAtom": "All", "renderBond": true, "bondScale": 0.2 }
{ "kind": "Atomistic", "visible": true, "size": { "sizeOption": "Uniform", "scale": 0.2 }, "coloring": { "kind": "Property", "carbonsOnly": true, "propertySource": "ChainInstance" }, "renderAtom": "All", "renderBond": true, "bondScale": 0.2 }
{ "kind": "Atomistic", "visible": true, "size": { "sizeOption": "Uniform", "scale": 0.1 }, "coloring": { "kind": "Property", "carbonsOnly": true, "propertySource": "ChainInstance" }, "renderAtom": "NonBondedOnly", "renderBond": true, "bondScale": 0.05 }
{ "kind": "Atomistic", "visible": true, "size": { "sizeOption": "Physical", "scale": 1.0 }, "coloring": { "kind": "Property", "carbonsOnly": true, "propertySource": "ChainInstance" }, "renderAtom": "All", "renderBond": false, "bondScale": 0 }
{ "kind": "Ribbon", "visible": true, "size": { "sizeOption": "Uniform", "scale": 1.0 }, "coloring": { "kind": "Property", "carbonsOnly": false, "propertySource": "ChainInstance" }, "ribbonStyle": "Cartoon" }
{ "kind": "MolecularSurface", "visible": true, "size": { "sizeOption": "Uniform", "scale": 1.0 }, "coloring": { "kind": "Property", "carbonsOnly": false, "propertySource": "ChainInstance" }, "opacity": 0.5, "subsurface": false }
{ "kind": "ResidueLabel", "visible": true, "size": { "sizeOption": "Uniform", "scale": 1.0 }, "coloring": { "kind": "Uniform", "carbonsOnly": false, "uniformColor": "FFFFFF" } }
{ "kind": "Property", "carbonsOnly": false, "propertySource": "BFactor", "paletteType": "Interpolated", "colors": ["0000FF", "7F7FFF", "FFFFFF", "FF7F7F", "FF0000"] }
{ "kind": "Property", "carbonsOnly": false, "propertySource": "AtomSerial", "paletteType": "Interpolated", "colors": ["8F00FF", "4B0082", "0000FF", "00FF00", "FFFF00", "FF7F00", "FF0000"] }
Default parameters for each interaction type:
| Interaction | uniformColor | size scale | bondScale | gapScale |
|---|---|---|---|---|
| HydrogenBonds | FFFF00 | 0.05 | 0.2 | 0.2 |
| PiCation | 0080FF | 0.06 | 0.2 | 0.2 |
| Clash | CC003D | 0.5 | 0.05 | 0.0 |
| Halogen | 3AE2AA | 0.05 | 0.15 | 0.25 |
| Hydrophobic | 808080 | 0.03 | 0.12 | 0.3 |
| MetalCoordination | A020F0 | 0.075 | 0.35 | 0.25 |
| PiStacking | 6443FF | 0.04 | 0.25 | 0.35 |
| SaltBridges | FF6600 | 0.06 | 0.3 | 0.15 |
Use this template for any interaction type, substituting values from the table above:
{
"kind": "Interaction",
"visible": true,
"interactionType": "HydrogenBonds",
"size": { "sizeOption": "Uniform", "scale": 0.05 },
"coloring": { "kind": "Uniform", "carbonsOnly": false, "uniformColor": "FFFF00" },
"bondScale": 0.2,
"gapScale": 0.2
}
Use the uniformColor, size scale, bondScale, and gapScale values from the table above for each interaction type.
Default: 1 scene. Only create multiple scenes when the user explicitly requests them.
Use this table to choose components for the single scene:
| User Goal | Components |
|---|---|
| General protein visualization | Protein: Cartoon/ChainInstance coloring. Ligands: Ball-and-stick/ElementType |
| Drug binding analysis | Protein: Cartoon. Ligand: Ball-and-stick. Binding residues: Stick (5A distance filter). Interactions: HydrogenBonds + Hydrophobic |
| Structure comparison | Each entry: Cartoon with distinct Uniform color |
| DNA/RNA-protein complex | Protein + nucleic acid: Cartoon with different colors |
| Small molecule library | Each ligand: Ball-and-stick, ElementType coloring |
| AlphaFold prediction | Cartoon colored by BFactor (pLDDT), Cold-to-Warm palette |
| Unrecognized / other | Fallback: use add-default per entry |
add-default as fallback when user's goal doesn't match any heuristicadd-default with multiple entries, call the per-entry route: POST /workspaces/{id}/scenes/{serial}/entries/{entrySerial}/components/add-defaultScene 1 "Protein Overview":
Component "Protein":
curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes/1/components/add" \
"${AUTH_ARGS[@]}" \
-H "Content-Type: application/json" \
-d @- <<'COMPONENT'
{
"name": "Protein",
"filters": [
{ "kind": "EntrySerial", "entrySerial": 1 },
{ "kind": "CurrentModel" },
{ "kind": "Substructure", "substructureType": "Protein" }
],
"visualizations": [
{
"kind": "Ribbon",
"visible": true,
"ribbonStyle": "Cartoon",
"size": { "sizeOption": "Uniform", "scale": 1.0 },
"coloring": {
"kind": "Property",
"carbonsOnly": false,
"propertySource": "ChainInstance"
}
}
]
}
COMPONENT
Component "Ligand":
curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes/1/components/add" \
"${AUTH_ARGS[@]}" \
-H "Content-Type: application/json" \
-d @- <<'COMPONENT'
{
"name": "Ligand",
"filters": [
{ "kind": "EntrySerial", "entrySerial": 1 },
{ "kind": "CurrentModel" },
{ "kind": "Substructure", "substructureType": "Ligand" }
],
"visualizations": [
{
"kind": "Atomistic",
"visible": true,
"size": { "sizeOption": "Physical", "scale": 0.25 },
"coloring": { "kind": "ElementType", "carbonsOnly": false },
"renderAtom": "All",
"renderBond": true,
"bondScale": 0.2
}
]
}
COMPONENT
Scene 2 "Binding Site":
First, create and rename the scene:
SCENE2=$(curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes" "${AUTH_ARGS[@]}")
SCENE2_SERIAL=$(echo "$SCENE2" | python3 -c "import sys,json; print(json.load(sys.stdin)['serial'])")
curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes/$SCENE2_SERIAL/rename" \
"${AUTH_ARGS[@]}" \
-H "Content-Type: application/json" \
-d '{"name": "Binding Site"}'
Component "Imatinib":
curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes/$SCENE2_SERIAL/components/add" \
"${AUTH_ARGS[@]}" \
-H "Content-Type: application/json" \
-d @- <<'COMPONENT'
{
"name": "Imatinib",
"filters": [
{ "kind": "EntrySerial", "entrySerial": 1 },
{ "kind": "CurrentModel" },
{ "kind": "Substructure", "substructureType": "Ligand" }
],
"visualizations": [
{
"kind": "Atomistic",
"visible": true,
"size": { "sizeOption": "Physical", "scale": 0.25 },
"coloring": { "kind": "ElementType", "carbonsOnly": false },
"renderAtom": "All",
"renderBond": true,
"bondScale": 0.2
}
]
}
COMPONENT
Component "Binding Residues":
curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes/$SCENE2_SERIAL/components/add" \
"${AUTH_ARGS[@]}" \
-H "Content-Type: application/json" \
-d @- <<'COMPONENT'
{
"name": "Binding Residues",
"filters": [
{
"kind": "Intersect",
"containedFilters": [
{ "kind": "EntrySerial", "entrySerial": 1 },
{ "kind": "CurrentModel" },
{
"kind": "Distance",
"distance": 5.0,
"wholeResidue": true,
"ignoreSelf": true,
"containedFilters": [
{ "kind": "Substructure", "substructureType": "Ligand" }
]
}
]
}
],
"visualizations": [
{
"kind": "Atomistic",
"visible": true,
"size": { "sizeOption": "Uniform", "scale": 0.2 },
"coloring": { "kind": "Property", "carbonsOnly": true, "propertySource": "ChainInstance" },
"renderAtom": "All",
"renderBond": true,
"bondScale": 0.2
}
]
}
COMPONENT
Component "Interactions" (hydrogen bonds):
curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes/$SCENE2_SERIAL/components/add" \
"${AUTH_ARGS[@]}" \
-H "Content-Type: application/json" \
-d @- <<'COMPONENT'
{
"name": "H-Bond Interactions",
"filters": [
{ "kind": "EntrySerial", "entrySerial": 1 },
{ "kind": "CurrentModel" },
{ "kind": "Substructure", "substructureType": "Ligand" }
],
"visualizations": [
{
"kind": "Interaction",
"visible": true,
"interactionType": "HydrogenBonds",
"size": { "sizeOption": "Uniform", "scale": 0.05 },
"coloring": { "kind": "Uniform", "carbonsOnly": false, "uniformColor": "FFFF00" },
"bondScale": 0.2,
"gapScale": 0.2
},
{
"kind": "Interaction",
"visible": true,
"interactionType": "Hydrophobic",
"size": { "sizeOption": "Uniform", "scale": 0.03 },
"coloring": { "kind": "Uniform", "carbonsOnly": false, "uniformColor": "808080" },
"bondScale": 0.12,
"gapScale": 0.3
}
]
}
COMPONENT
Scene 3 "Surface View":
SCENE3=$(curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes" "${AUTH_ARGS[@]}")
SCENE3_SERIAL=$(echo "$SCENE3" | python3 -c "import sys,json; print(json.load(sys.stdin)['serial'])")
curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes/$SCENE3_SERIAL/rename" \
"${AUTH_ARGS[@]}" \
-H "Content-Type: application/json" \
-d '{"name": "Surface View"}'
Component "Protein Surface":
curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes/$SCENE3_SERIAL/components/add" \
"${AUTH_ARGS[@]}" \
-H "Content-Type: application/json" \
-d @- <<'COMPONENT'
{
"name": "Protein Surface",
"filters": [
{ "kind": "EntrySerial", "entrySerial": 1 },
{ "kind": "CurrentModel" },
{ "kind": "Substructure", "substructureType": "Protein" }
],
"visualizations": [
{
"kind": "MolecularSurface",
"visible": true,
"size": { "sizeOption": "Uniform", "scale": 1.0 },
"coloring": { "kind": "Property", "carbonsOnly": false, "propertySource": "ChainInstance" },
"opacity": 0.5,
"subsurface": false
}
]
}
COMPONENT
Component "Ligand in Surface":
curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes/$SCENE3_SERIAL/components/add" \
"${AUTH_ARGS[@]}" \
-H "Content-Type: application/json" \
-d @- <<'COMPONENT'
{
"name": "Ligand in Surface",
"filters": [
{ "kind": "EntrySerial", "entrySerial": 1 },
{ "kind": "CurrentModel" },
{ "kind": "Substructure", "substructureType": "Ligand" }
],
"visualizations": [
{
"kind": "Atomistic",
"visible": true,
"size": { "sizeOption": "Physical", "scale": 0.25 },
"coloring": { "kind": "ElementType", "carbonsOnly": false },
"renderAtom": "All",
"renderBond": true,
"bondScale": 0.2
}
]
}
COMPONENT
Scene 1 "Comparison":
Rename the auto-created scene 1:
curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes/1/rename" \
"${AUTH_ARGS[@]}" \
-H "Content-Type: application/json" \
-d '{"name": "Comparison"}'
Component "Structure 1" (blue):
curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes/1/components/add" \
"${AUTH_ARGS[@]}" \
-H "Content-Type: application/json" \
-d @- <<'COMPONENT'
{
"name": "Structure 1",
"filters": [
{ "kind": "EntrySerial", "entrySerial": 1 },
{ "kind": "CurrentModel" },
{ "kind": "Substructure", "substructureType": "Protein" }
],
"visualizations": [
{
"kind": "Ribbon",
"visible": true,
"ribbonStyle": "Cartoon",
"size": { "sizeOption": "Uniform", "scale": 1.0 },
"coloring": {
"kind": "Uniform",
"carbonsOnly": false,
"uniformColor": "4287f5"
}
}
]
}
COMPONENT
Component "Structure 2" (red):
curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes/1/components/add" \
"${AUTH_ARGS[@]}" \
-H "Content-Type: application/json" \
-d @- <<'COMPONENT'
{
"name": "Structure 2",
"filters": [
{ "kind": "EntrySerial", "entrySerial": 2 },
{ "kind": "CurrentModel" },
{ "kind": "Substructure", "substructureType": "Protein" }
],
"visualizations": [
{
"kind": "Ribbon",
"visible": true,
"ribbonStyle": "Cartoon",
"size": { "sizeOption": "Uniform", "scale": 1.0 },
"coloring": {
"kind": "Uniform",
"carbonsOnly": false,
"uniformColor": "f54242"
}
}
]
}
COMPONENT
| Action | Method | Endpoint | Body/Notes |
|---|---|---|---|
| Create workspace | POST | /workspaces | { "name": "...", "description": "..." }. Returns { "id": "GUID", "name": "..." } |
| Edit workspace | POST | /workspaces/{id} | { "name": "...", "description": "..." } |
| Load structure | POST | /workspaces/{id}/entries/load | Multipart file upload. Returns [{ "serial": 1, "name": "..." }] (array) |
| Rename entry | POST | /workspaces/{id}/entries/{serial}/rename | { "name": "..." } |
| Create scene | POST | /workspaces/{id}/scenes | Returns single object { "serial": 2, "name": "Scene" } (NOT an array) |
| Rename scene | POST | /workspaces/{id}/scenes/{serial}/rename | { "name": "..." } |
| Add default components (scene-wide) | POST | /workspaces/{id}/scenes/{serial}/components/add-default | Adds defaults for all entries |
| Add default components (per-entry) | POST | /workspaces/{id}/scenes/{serial}/entries/{entrySerial}/components/add-default | Preferred for multi-entry workspaces |
| Add custom component | POST | /workspaces/{id}/scenes/{serial}/components/add | Single ComponentInput JSON (not array) |
All endpoints require Authorization: Bearer {token} when auth is active.
#!/bin/bash
# Nanome Workspace: {NAME}
# Structures: {list}
# Scenes: {list}
#
# Requirements: curl, python3
# Usage: bash create_workspace.sh
# Or: API_BASE=... TOKEN=... bash create_workspace.sh
set -e
# --- Config ---
CONFIG_FILE="$HOME/.nanome/config.json"
if [ -f "$CONFIG_FILE" ]; then
API_BASE=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('api_base', 'https://workspaces.nanome.ai'))")
TOKEN=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('token', ''))")
fi
API_BASE="${API_BASE:-https://workspaces.nanome.ai}"
AUTH_ARGS=()
if [ -n "$TOKEN" ]; then
AUTH_ARGS=(-H "Authorization: Bearer $TOKEN")
fi
download_structure() {
local url="$1" output="$2"
local http_code
http_code=$(curl -s -o "$output" -w "%{http_code}" "$url")
if [ "$http_code" != "200" ] || [ ! -s "$output" ]; then
echo "ERROR: Failed to download $url (HTTP $http_code)" >&2
rm -f "$output"
return 1
fi
}
# --- Create Workspace ---
echo "Creating workspace: {NAME}..."
WORKSPACE=$(curl -s -X POST "$API_BASE/workspaces" \
"${AUTH_ARGS[@]}" \
-H "Content-Type: application/json" \
-d '{"name": "{NAME}", "description": "{DESCRIPTION}"}')
WORKSPACE_ID=$(echo "$WORKSPACE" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
echo "Workspace ID: $WORKSPACE_ID"
# --- Download & Upload Structures ---
WORKDIR=$(mktemp -d)
trap 'rm -rf "$WORKDIR"' EXIT
echo "Downloading {PDB_ID}..."
download_structure "https://files.rcsb.org/download/{PDB_ID}.pdb" "$WORKDIR/{PDB_ID}.pdb"
echo "Uploading {PDB_ID}..."
ENTRY=$(curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/entries/load" \
"${AUTH_ARGS[@]}" \
-F "file=@$WORKDIR/{PDB_ID}.pdb")
ENTRY_SERIAL=$(echo "$ENTRY" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d[0]['serial'] if isinstance(d,list) else d['serial'])")
# --- Scene 1 (auto-created, rename it) ---
curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes/1/rename" \
"${AUTH_ARGS[@]}" \
-H "Content-Type: application/json" \
-d '{"name": "{SCENE_1_NAME}"}'
# --- Add components to Scene 1 ---
curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes/1/components/add" \
"${AUTH_ARGS[@]}" \
-H "Content-Type: application/json" \
-d @- <<'COMPONENT'
{JSON component payload}
COMPONENT
# --- Create Scene 2 ---
SCENE2=$(curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes" \
"${AUTH_ARGS[@]}")
SCENE2_SERIAL=$(echo "$SCENE2" | python3 -c "import sys,json; print(json.load(sys.stdin)['serial'])")
curl -s -X POST "$API_BASE/workspaces/$WORKSPACE_ID/scenes/$SCENE2_SERIAL/rename" \
"${AUTH_ARGS[@]}" \
-H "Content-Type: application/json" \
-d '{"name": "{SCENE_2_NAME}"}'
# --- Output ---
echo ""
echo "Workspace created successfully!"
echo "URL: https://mara.nanome.ai/workspaces/$WORKSPACE_ID"
# Cleanup handled by trap on EXIT
When nanome_* MCP tools are available (Claude Chat with Nanome MCP server), use tool calls instead of generating a bash script. The component JSON is identical — only the execution mechanism changes.
nanome_create_workspace({ name, description }) → get workspace_idnanome_load_structure({ workspace_id, source, id }) → get serialnanome_rename_scene({ workspace_id, scene_serial: 1, name })nanome_add_component({ workspace_id, scene_serial: 1, component }) — one call per componentnanome_create_scene({ workspace_id }) → get serial, then nanome_rename_scene, then nanome_add_component for each componentnanome_create_workspace({ name: "ABL Kinase — Imatinib", description: "Drug binding analysis" })
→ { id: "abc-123", name: "ABL Kinase — Imatinib" }
nanome_load_structure({ workspace_id: "abc-123", source: "rcsb", id: "1IEP" })
→ { serial: 1, name: "1IEP" }
nanome_rename_scene({ workspace_id: "abc-123", scene_serial: 1, name: "Protein Overview" })
nanome_add_component({ workspace_id: "abc-123", scene_serial: 1, component: {
"name": "Protein",
"filters": [
{ "kind": "EntrySerial", "entrySerial": 1 },
{ "kind": "CurrentModel" },
{ "kind": "Substructure", "substructureType": "Protein" }
],
"visualizations": [{
"kind": "Ribbon", "visible": true, "ribbonStyle": "Cartoon",
"size": { "sizeOption": "Uniform", "scale": 1.0 },
"coloring": { "kind": "Property", "carbonsOnly": false, "propertySource": "ChainInstance" }
}]
}})
nanome_add_component({ workspace_id: "abc-123", scene_serial: 1, component: {
"name": "Ligand",
"filters": [
{ "kind": "EntrySerial", "entrySerial": 1 },
{ "kind": "CurrentModel" },
{ "kind": "Substructure", "substructureType": "Ligand" }
],
"visualizations": [{
"kind": "Atomistic", "visible": true,
"size": { "sizeOption": "Physical", "scale": 0.25 },
"coloring": { "kind": "ElementType", "carbonsOnly": false },
"renderAtom": "All", "renderBond": true, "bondScale": 0.2
}]
}})
nanome_create_scene({ workspace_id: "abc-123" })
→ { serial: 2, name: "Scene" }
nanome_rename_scene({ workspace_id: "abc-123", scene_serial: 2, name: "Binding Site" })
nanome_add_component({ workspace_id: "abc-123", scene_serial: 2, component: {
"name": "Imatinib",
"filters": [
{ "kind": "EntrySerial", "entrySerial": 1 },
{ "kind": "CurrentModel" },
{ "kind": "Substructure", "substructureType": "Ligand" }
],
"visualizations": [{
"kind": "Atomistic", "visible": true,
"size": { "sizeOption": "Physical", "scale": 0.25 },
"coloring": { "kind": "ElementType", "carbonsOnly": false },
"renderAtom": "All", "renderBond": true, "bondScale": 0.2
}]
}})
nanome_add_component({ workspace_id: "abc-123", scene_serial: 2, component: {
"name": "Binding Residues",
"filters": [{
"kind": "Intersect",
"containedFilters": [
{ "kind": "EntrySerial", "entrySerial": 1 },
{ "kind": "CurrentModel" },
{ "kind": "Distance", "distance": 5.0, "wholeResidue": true, "ignoreSelf": true,
"containedFilters": [{ "kind": "Substructure", "substructureType": "Ligand" }] }
]
}],
"visualizations": [{
"kind": "Atomistic", "visible": true,
"size": { "sizeOption": "Uniform", "scale": 0.2 },
"coloring": { "kind": "Property", "carbonsOnly": true, "propertySource": "ChainInstance" },
"renderAtom": "All", "renderBond": true, "bondScale": 0.2
}]
}})
nanome_add_component({ workspace_id: "abc-123", scene_serial: 2, component: {
"name": "H-Bond Interactions",
"filters": [
{ "kind": "EntrySerial", "entrySerial": 1 },
{ "kind": "CurrentModel" },
{ "kind": "Substructure", "substructureType": "Ligand" }
],
"visualizations": [{
"kind": "Interaction", "visible": true, "interactionType": "HydrogenBonds",
"size": { "sizeOption": "Uniform", "scale": 0.06 },
"coloring": { "kind": "Uniform", "carbonsOnly": false, "uniformColor": "FFFF00" },
"bondScale": 0.2, "gapScale": 0.2
}]
}})
nanome_load_structure handles both internally~/.nanome/config.json or env varsfilters, visualizations, coloring objects as Script ModeWhen neither Bash nor nanome_* MCP tools are available (Claude Chat without MCP server), generate a self-contained HTML artifact that builds the workspace in the user's browser.
Generate an HTML artifact using the template below. Fill in the four data constants (WORKSPACE_NAME, WORKSPACE_DESCRIPTION, STRUCTURES, SCENES) with the resolved workspace data. The SCENES array uses the exact same component JSON schema as Script Mode and MCP Mode.
The user clicks "Build Workspace" in the artifact, enters their Nanome token, and the JavaScript executes all API calls sequentially with progress logging.
| Placeholder | Type | Example |
|---|---|---|
WORKSPACE_NAME | string | "ABL Kinase — Imatinib" |
WORKSPACE_DESCRIPTION | string | "Drug binding analysis" |
STRUCTURES | array of { source, id, filename } | [{ source: "rcsb", id: "1IEP", filename: "1IEP.pdb" }] |
SCENES | array of { name, components: ComponentInput[] } | [{ name: "Overview", components: [/* same JSON as API */] }] |
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Nanome Workspace Builder</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, system-ui, sans-serif; max-width: 640px; margin: 40px auto; padding: 0 20px; color: #1a1a1a; }
h2 { margin-bottom: 16px; }
.field { margin-bottom: 12px; }
.field label { display: block; font-weight: 600; margin-bottom: 4px; font-size: 14px; }
.field input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; }
button { background: #2563eb; color: white; border: none; padding: 10px 20px; border-radius: 4px; font-size: 14px; cursor: pointer; }
button:disabled { background: #93b4f5; cursor: not-allowed; }
#log { margin-top: 16px; padding: 12px; background: #f5f5f5; border-radius: 4px; font-family: monospace; font-size: 13px; white-space: pre-wrap; max-height: 400px; overflow-y: auto; display: none; }
.success { color: #16a34a; font-weight: 600; }
.error { color: #dc2626; font-weight: 600; }
#result { margin-top: 16px; display: none; }
#result a { color: #2563eb; font-weight: 600; }
</style>
</head>
<body>
<h2>/* WORKSPACE_NAME */</h2>
<p style="margin-bottom:16px;color:#666">/* WORKSPACE_DESCRIPTION */</p>
<div class="field">
<label>Nanome Token (<a href="https://mara.nanome.ai/login" target="_blank">get token</a>)</label>
<input type="password" id="token" placeholder="Paste your token here" />
</div>
<button id="btn" onclick="build()">Build Workspace</button>
<div id="result"></div>
<pre id="log"></pre>
<script>
const API = 'https://workspaces.nanome.ai';
const WORKSPACE_NAME = '/* fill in */';
const WORKSPACE_DESCRIPTION = '/* fill in */';
const STRUCTURES = /* fill in: [{ source: "rcsb", id: "1IEP", filename: "1IEP.pdb" }] */;
const SCENES = /* fill in: [{ name: "Overview", components: [{ name: "Protein", filters: [...], visualizations: [...] }] }] */;
const SOURCE_URLS = {
rcsb: (id) => `https://files.rcsb.org/download/${id}.pdb`,
alphafold: (id) => `https://alphafold.ebi.ac.uk/files/AF-${id}-F1-model_v4.pdb`,
pubchem: (id) => `https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/CID/${id}/SDF`,
chembl: (id) => `https://www.ebi.ac.uk/chembl/api/data/molecule/${id}.sdf`,
swissmodel: (id) => `https://swissmodel.expasy.org/repository/uniprot/${id}.pdb`,
cod: (id) => `https://www.crystallography.net/cod/${id}.cif`,
};
const logEl = document.getElementById('log');
const btn = document.getElementById('btn');
const resultEl = document.getElementById('result');
function appendLog(msg, cls) {
logEl.style.display = 'block';
const span = document.createElement('span');
if (cls) span.className = cls;
span.textContent = msg + '\n';
logEl.appendChild(span);
logEl.scrollTop = logEl.scrollHeight;
}
function showResult(wsId) {
resultEl.style.display = 'block';
resultEl.textContent = '';
const label = document.createElement('strong');
label.textContent = 'Workspace URL: ';
const link = document.createElement('a');
link.href = `https://mara.nanome.ai/workspaces/${wsId}`;
link.target = '_blank';
link.textContent = `https://mara.nanome.ai/workspaces/${wsId}`;
resultEl.appendChild(label);
resultEl.appendChild(link);
}
async function apiPost(path, body, token, isFormData) {
const headers = {};
if (token) headers['Authorization'] = `Bearer ${token}`;
if (!isFormData) headers['Content-Type'] = 'application/json';
const opts = { method: 'POST', headers };
if (body) opts.body = isFormData ? body : JSON.stringify(body);
const res = await fetch(`${API}${path}`, opts);
if (!res.ok) {
const text = await res.text();
if (res.status === 401) throw new Error('Authentication failed (401). Check your token or get a new one at https://mara.nanome.ai/login');
throw new Error(`API error ${res.status}: ${text}`);
}
return res.json();
}
async function build() {
const token = document.getElementById('token').value.trim();
if (!token) { alert('Please enter your Nanome token'); return; }
btn.disabled = true;
logEl.textContent = '';
resultEl.style.display = 'none';
try {
// 1. Create workspace
appendLog(`Creating workspace: ${WORKSPACE_NAME}...`);
const ws = await apiPost('/workspaces', { name: WORKSPACE_NAME, description: WORKSPACE_DESCRIPTION }, token);
const wsId = ws.id;
appendLog(`Workspace created: ${wsId}`, 'success');
showResult(wsId);
// 2. Load structures
for (const struct of STRUCTURES) {
try {
const url = SOURCE_URLS[struct.source](struct.id);
appendLog(`Downloading ${struct.source}:${struct.id}...`);
const dlRes = await fetch(url);
if (!dlRes.ok) throw new Error(`Download failed: ${dlRes.status}`);
const blob = await dlRes.blob();
const form = new FormData();
form.append('file', blob, struct.filename);
appendLog(`Uploading ${struct.filename}...`);
const entry = await apiPost(`/workspaces/${wsId}/entries/load`, form, token, true);
const serial = Array.isArray(entry) ? entry[0].serial : entry.serial;
appendLog(`Loaded entry serial ${serial}`, 'success');
} catch (err) {
appendLog(`Warning: Failed to load ${struct.source}:${struct.id} — ${err.message}`, 'error');
}
}
// 3. Build scenes
for (let i = 0; i < SCENES.length; i++) {
const scene = SCENES[i];
let sceneSerial;
if (i === 0) {
sceneSerial = 1;
appendLog(`Renaming scene 1 to "${scene.name}"...`);
await apiPost(`/workspaces/${wsId}/scenes/1/rename`, { name: scene.name }, token);
} else {
appendLog(`Creating scene "${scene.name}"...`);
const s = await apiPost(`/workspaces/${wsId}/scenes`, null, token);
sceneSerial = s.serial;
await apiPost(`/workspaces/${wsId}/scenes/${sceneSerial}/rename`, { name: scene.name }, token);
}
for (const comp of scene.components) {
appendLog(` Adding component: ${comp.name}...`);
await apiPost(`/workspaces/${wsId}/scenes/${sceneSerial}/components/add`, comp, token);
}
appendLog(`Scene "${scene.name}" complete`, 'success');
}
appendLog('\nWorkspace build complete!', 'success');
} catch (err) {
appendLog(`\nBuild failed: ${err.message}`, 'error');
} finally {
btn.disabled = false;
}
}
</script>
</body>
</html>
https://mara.nanome.ai/loginVerified CORS support: RCSB (files.rcsb.org), PubChem, AlphaFold. Unverified: ChEMBL, SWISS-MODEL, COD — if these fail in browser, the artifact logs a warning and continues. MCP Mode is unaffected (server-side downloads).
After executing the workspace creation script, produce three things: