# Project export: NovoProteinAI

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2026
- Tagline: Today we are Vibe-Coding. Tomorrow we are Vibe-Protein-Designing. This is NovoProteinAI, a tool that visualizes proteins, highlights epitopes, and runs QC by connecting PyMOL and agents
- Devpost: https://devpost.com/software/novoproteinai
- GitHub: https://github.com/santosrai/NovoProteinAI/
- Video: https://www.youtube.com/embed/2i5Ee7LFxrU?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — mesantos (6 commits), Pancake15 (3 commits), Claude Opus 4.8 (1 commits)

## Devpost submission (written by the team)

### Overview

Problem: Protein design sits at the center of modern medicine — cancer immunotherapy, gene editing, next-generation antibodies all depend on it. It's also one of AI's fastest-growing applications: the U.S. AI protein design market is already valued around $420 million and projected to grow to $6–15 billion globally by 2030, accelerated by breakthroughs like AlphaFold. But the tooling hasn't kept pace. PyMOL is the de facto standard everyone in structural biology uses to visually inspect and analyze proteins, yet it requires memorizing dozens of terse text commands where even small mistakes (color red, chain A vs. chain A, color red) break the command. Working scientists lose real time every day looking up syntax, manually inspecting designs for errors, and cross-checking outputs by hand — time that should go toward the actual science. Our solution: NovoProteinAI lets you control PyMOL using plain English instead of memorized commands, so you can move from question to answer fluidly instead of wasting hours on syntax-wrangling. You type something like "highlight the binding interface between chain A and chain B" or "show me the epitope this antibody is targeting" — and an AI agent does the rest. It interprets your request, pulls in relevant research from scientific papers that would’ve required a large investment in time, and runs the correct sequence of PyMOL commands to generate the visualization instantly. It also acts as an automated QC and analysis layer. It checks that what you designed is what actually came out, for example verifying the design is the right format (antibody, nanobody, scFv) and confirming it's actually targeting the epitope you specified. This catches mismatches before they cost you a wasted design cycle. It can compare multiple design outputs side by side, flagging clashes, measuring interface size, and scoring quality, with a plain-language readout of what each metric means so you can make a faster call on which design to move forward with. This isn't a simplified or training-wheels version of the tool — it's the same PyMOL power, accessed faster, with automated checks layered on top that catch errors a human might miss on a manual pass. The result: less time fighting tool syntax and manually inspecting structures, more time iterating on designs — more designs reviewed per day, fewer costly mistakes carried forward. Architecture

### How we built it

: Fetch.ai uAgents — agent lives on Agentverse, reachable by anyone on the network via the ACP Chat Protocol (UI) PyMOL MCP Bridge — FastMCP server over stdio bridges the agent to a TCP plugin running inside PyMOL, enabling real-time command execution Claude Code — Vibe-coding Tools, LLM for understanding the user input from Agentverse Redis — the key to understanding research papers, whether they are uploaded by the user or found by the AI Agent, is implemented with a RAG pipeline utilizing Redis’ Vector Database Cognition (Devin) — The hero to our vibe-coding journey RunPod — for deploying the virtual machine hosting PyMOL

### Challenges we ran into

: Agentverse: ACP Chat Protocol has two layers — uAgents messaging and the Agentverse Chat Protocol are separate. Every message needs an acknowledgement first, and replies must use TextContent — miss either and Agentverse silently drops the message. Agentverse needs 10 interactions before the agent goes live — wrote trigger_interactions.py to send test messages automatically, which also worked as a smoke test. Public URL breaks when ngrok restarts — every new ngrok URL means re-running register_agent.py. No auto-update. Chat protocol compliant — other agents and users can interact via standard UI Regex parser broke on natural word order: color red, chain A vs. chain A, color red is the same but when i send to PyMOL MCP, it is the same so that I use Redis: We had some connection issues with Redis Cloud, so we decided to pivot to a local Redis vector database. This removed the wifi connection variable, which could have impacted our ability to connect to the cloud consistently. Ensuring that the embeddings did not split vital information between two chunks was another challenge. To address this issue, we implemented a regex and semantic chunking to ensure that the content of each chunk was split at logical points in the text. We wanted to have both the context of the chunk and the PDB ID (which is the code that refers to a specific protein structure). We separated this process into two steps: run Redis’ vector search that uses cosine similarity to retrieve a relevant chunk, and then use that chunk’s paper ID to locate the PDB ID in the paper.

### Accomplishments we're proud of

: Bridging interdisciplinary skills together to create a working product. Enabling non-biologist to load, color, and render a real protein structure in under 30 seconds with zero biology knowledge Implement a complete, working RAG-pipeline Implement an MCP server for PyMOL that our AI Agent can connect to

### What we learned

: MCP is a clean abstraction layer between AI and tools, the agent never needs to know how PyMOL works internally. Fetch.ai's agent network changes what "deployed" means: once the agent is on Agentverse, anyone on the network can reach it without any client setup, API keys, or SDKs on their end. Distribution is built into the infrastructure. Vibe-coding tools like Claude Code or Devin is real but require direction from the user, user now is like a product designer, thinking more about the product than normal coding Scope fast, cut faster — a hackathon forces you to decide what's core and what's nice-to-have within hours. The hardest part isn't the code — it's the integration between systems. Each piece worked in isolation. Getting them to talk to each other reliably was where most time went.

### What's next

: We want to continue this path of making protein-design accessible to the general public, much like how vibe-coding is starting to become more and more accessible. Currently, we have the research and visualization parts of the protein development process built. We intend to continue to add tools and features like incorporating the RFDiffusion model and protein ESMFold to give user the end-to-end experience of protein development.

## README (from the GitHub repository)

# NovoProteinAI

A tool that uses PyMOL as typed tools to LLM agents like Claude Desktop, Cline, and Devin using MCP and pymol plugin

## Architecture

```
Claude/Devin (MCP client)
    ↕ stdio
FastMCP Server (pymol_mcp/)
    ↕ TCP (localhost:9877, JSON-RPC 2.0)
PyMOL Plugin (pymol_plugin/)
    ↕ PyMOL API
PyMOL Session
```

The bridge consists of two components:

1. **PyMOL Plugin** - TCP server running inside PyMOL that executes commands
2. **MCP Server** - FastMCP server that exposes PyMOL functionality as MCP tools

## Features

- 🔌 **5 Core Tools**: Load structures, select atoms, color, render images, health check
- 🔄 **Auto-reconnect**: Exponential backoff retry logic
- ⚙️ **Configurable**: Environment variables or YAML config file
- 🛡️ **Error Handling**: Comprehensive error handling and logging
- 📡 **JSON-RPC 2.0**: Length-prefixed wire protocol over TCP

## Installation

### 1. Install MCP Server

```bash
cd NovoProteinAI
pip install -e .
```

### 2. Install PyMOL Plugin

See [PLUGIN_INSTALL.md](PLUGIN_INSTALL.md) for detailed instructions.

**Quick method:**
1. Open PyMOL
2. Go to `Plugin` → `Plugin Manager`
3. Click `Install New Plugin` → `Choose file...`
4. Select `pymol_plugin/__init__.py`
5. Click `OK` to install

### 3. Configure MCP Client

See [MCP_CONFIG.md](MCP_CONFIG.md) for client-specific configuration.

**For Claude Desktop**, add to `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "pymol": {
      "command": "python",
      "args": ["-m", "pymol_mcp.server"],
      "env": {
        "PYMOL_HOST": "localhost",
        "PYMOL_PORT": "9877"
      }
    }
  }
}
```

## Usage

### 1. Start PyMOL Plugin

1. Open PyMOL
2. Go to `Plugin` → `agentic-pymol plugin` → `Control Panel`
3. Click **Start Server** and watch the live status + activity log

### 2. Use from LLM Agent

The MCP server exposes 5 tools:

#### `load_structure`
Load a molecular structure from file or PDB ID.
```python
load_structure(source="/path/to/protein.pdb")
load_structure(source="1ABC", object_name="my_protein")
```

#### `select_atoms`
Create a named selection of atoms.
```python
select_atoms(selection_name="active_site", selection_expr="resi 100-150")
select_atoms(selection_name="backbone", selection_expr="name CA+C+N+O")
```

#### `color_selection`
Apply color to a selection.
```python
color_selection(color="red", selection="chain A")
color_selection(color="blue")  # colors all
```

#### `render_image`
Save an image of the current view.
```python
render_image(output_path="/tmp/protein.png", width=1920, height=1080)
render_image(output_path="/tmp/hq.png", ray_trace=True)
```

#### `ping_pymol`
Check connection and get PyMOL version.
```python
ping_pymol()
```

## Research Agent (Fetch.ai / Agentverse)

`src/research_agent.py` is a Fetch.ai uAgent that turns a plain-English
vaccine/therapeutic goal into a structured target result sourced from public
biology databases (RCSB PDB + PubMed). Its JSON output matches the inputs of
`render_image()` in `visualize.py`.

### Output contract

```json
{
  "target_name": "SARS-CoV-2 spike receptor-binding domain",
  "pdb_id": "6VXX",
  "chain": "A",
  "epitope_residues": [417, 484, 501],
  "binder_pdb_ids": ["7K8M"],
  "explanation": "Plain-English summary a non-biologist can follow.",
  "citations": [{"title": "...", "pmid": "...", "url": "https://pubmed.ncbi.nlm.nih.gov/..."}]
}
```

### Run it

```bash
pip install -r requirements.txt
export AGENT_SEED="some-fixed-phrase"     # required: stable agent address
export ASI_ONE_API_KEY="..."              # optional: falls back to keyword parsing
python src/research_agent.py
```

### Register on Agentverse

1. Run the agent — it prints an Agentverse Inspector/mailbox link.
2. Sign in at [agentverse.ai](https://agentverse.ai) and connect the mailbox so
   the agent is reachable without a public IP.
3. `publish_manifest=True` advertises chat capability, making it discoverable
   from [ASI:One](https://asi1.ai).
4. Test by messaging the agent: `build a vaccine for COVID`.

### Environment variables

- `AGENT_SEED` — fixed seed phrase for a stable agent address (required to run).
- `ASI_ONE_API_KEY` — ASI:One key for goal parsing + explanation drafting (optional).

> Note: `epitope_residues` is currently returned empty (IEDB lookup is a planned
> follow-up).

## Configuration

### Environment Variables

- `PYMOL_HOST` - PyMOL plugin host (default: `localhost`)
- `PYMOL_PORT` - PyMOL plugin port (default: `9877`)
- `PYMOL_TIMEOUT` - Request timeout in seconds (default: `30.0`)
- `PYMOL_RECONNECT_ATTEMPTS` - Number of reconnection attempts (default: `3`)
- `PYMOL_RECONNECT_DELAY` - Initial reconnection delay in seconds (default: `1.0`)
- `PYMOL_LOG_LEVEL` - Logging level (default: `INFO`)

### Configuration File

Create `config.yaml`:

```yaml
host: localhost
port: 9877
timeout: 30.0
reconnect_attempts: 3
reconnect_delay: 1.0
log_level: INFO
```

Run with config file:
```bash
python -m pymol_mcp.server --config config.yaml
```

## Development

### Project Structure

```
NovoProteinAI/
├── pymol_plugin/
│   └── __init__.py          # PyMOL plugin (TCP server)
├── pymol_mcp/
│   ├── __init__.py
│   ├── server.py            # FastMCP server entry point
│   ├── client.py            # TCP client for PyMOL
│   ├── config.py            # Configuration management
│   └── tools.py             # MCP tool definitions
├── tests/
│   ├── test_protocol.py
│   ├── test_client.py
│   └── test_tools.py
├── src/                    # Chat interface
├── requirements.txt
├── pyproject.toml
└── README.md
```

### Running Tests

```bash
pip install -e ".[dev]"
pytest tests/
```

### Manual Testing

```bash
# Terminal 1: Start PyMOL and enable plugin
pymol
# In PyMOL: Plugin → agentic-pymol plugin → Control Panel → Start Server

# Terminal 2: Test MCP server
python -m pymol_mcp.server
```

## Wire Protocol

The bridge uses **JSON-RPC 2.0** over TCP with length-prefixed messages:

```
[4-byte big-endian length][JSON payload]
```

Example request:
```json
{
  "jsonrpc": "2.0",
  "method": "load_structure",
  "params": {"source": "1ABC"},
  "id": 1
}
```

Example response:
```json
{
  "jsonrpc": "2.0",
  "result": {"message": "Loaded structure: 1ABC", "object_name": "1ABC"},
  "id": 1
}
```

## Troubleshooting

### Connection Refused
- Ensure PyMOL is running
- Verify plugin is started: `Plugin` → `agentic-pymol plugin` → `Server Status`
- Check port is not in use: `lsof -i :9877`

### Plugin Not Loading
- Check PyMOL console for errors
- Verify Python version compatibility (≥3.8)
- Try reinstalling plugin

### MCP Server Not Responding
- Check logs for connection errors
- Verify configuration (host/port)
- Test connection: `telnet localhost 9877`

## License

MIT

## Contributing

Contributions welcome! Please open an issue or PR.


## Detected evidence (automated analysis)

Indexed codebase: 26 recognized source files, 144 KB.
- Python (language) — detected in the code
- Redis (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (34 of 34)

```
.env.example
.gitignore
config.yaml.example
DEVIN_TASKS.md
IMPLEMENTATION_STATUS.md
LICENSE
mcp_config.json
MCP_CONFIG.md
PLUGIN_INSTALL.md
PROJECT_SUMMARY.md
pymol_mcp/__init__.py
pymol_mcp/client.py
pymol_mcp/config.py
pymol_mcp/server.py
pymol_mcp/tools.py
pymol_plugin/__init__.py
pyproject.toml
pytest.ini
QUICKSTART.md
README.md
requirements.txt
setup.sh
src/__init__.py
src/agent_graph.py
src/register_agent.py
src/research_agent.py
tests/__init__.py
tests/integration_test.py
tests/test_client.py
tests/test_load_structure.py
tests/test_protocol.py
tests/test_research_agent.py
tests/test_tools.py
verify_install.py
```

### Dependencies

- pyproject.toml: fastmcp@>=0.1.0, pytest@>=7.0, pytest-asyncio@>=0.21, pyyaml@>=6.0
- requirements.txt: fastmcp@>=0.1.0, langchain-anthropic@>=0.2.0, langchain-core@>=0.3.0, langchain-mcp-adapters@>=0.1.0, langgraph@>=0.2.0, python-dotenv@>=1.0.0, pyyaml@>=6.0, requests@>=2.31.0, uagents@>=0.12.0, uagents-core

### Recent commits (newest first)

- Add binder comparison workflow + conversation memory; remove visualize.py
- Add local file support to load_structure with path resolution and tests
- Merge pull request #2 from santosrai/dev-santos
- Add research agent with Fetch.ai/Agentverse integration and rotate tool
- Rename project from PyMOL MCP Bridge to NovoProteinAI and update description
- Merge pull request #1 from santosrai/dev-santos
- Merge remote-tracking branch 'origin/main' into dev-santos
- init
- Add PyMOL visualization layer and Devin task spec

## Key source files (fetched from GitHub, selected and truncated for size)

### QUICKSTART.md

```markdown
# Quick Start Guide

Get the PyMOL MCP Bridge running in 5 minutes.

## 1. Install Dependencies

```bash
cd NovoProteinAI
pip install -e .
```

## 2. Install PyMOL Plugin

**Option A: Plugin Manager (Recommended)**
1. Open PyMOL
2. `Plugin` → `Plugin Manager`
3. `Install New Plugin` → Choose `pymol_plugin/__init__.py`
4. Restart PyMOL

**Option B: Manual**
```bash
# macOS
cp pymol_plugin/__init__.py ~/Library/Application\ Support/PyMOL/startup/pymol_mcp_plugin.py

# Linux
cp pymol_plugin/__init__.py ~/.pymol/startup/pymol_mcp_plugin.py
```

## 3. Start PyMOL Plugin

1. Open PyMOL
2. `Plugin` → `agentic-pymol plugin` → `Control Panel`
3. Click **Start Server** — the status turns green and the activity log goes live

The panel shows: `Status: Running`, `Port: 9877`, `Connected clients: 0`

## 4. Configure MCP Client

### Claude Desktop

Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "pymol": {
      "command": "python",
      "args": ["-m", "pymol_mcp.server"]
    }
  }
}
```

Restart Claude Desktop.

### Other Clients

See [MCP_CONFIG.md](MCP_CONFIG.md) for Cline, Devin, etc.

## 5. Test It!

In Claude Desktop (or your MCP client):

```
Can you ping PyMOL?
```

Expected: `✓ Connected to PyMOL (version: X.X.X)`

```
Load PDB structure 1ABC and color it red
```

Expected: Structure loads in PyMOL and turns red!

## Available Tools

1. **`load_structure`** - Load PDB files or fetch from PDB database
2. **`select_atoms`** - Create named selections with PyMOL syntax
3. **`color_selection`** - Apply colors to selections
4. **`render_image`** - Save PNG images (with optional ray tracing)
5. **`ping_pymol`** - Check connection status

## Example Workflows

### Load and Visualize
```
Load PDB 1ABC, select chain A, color it blue, and save an image to /tmp/protein.png
```

### Compare Structures
```
Load 1ABC as protein1 and 2XYZ as protein2, color protein1 red and protein2 blue
```

### High-Quality Rendering
```
Load 1ABC, zoom to chain A, and render a ray-traced image at 1920x1080 to /tmp/hq.png
```

## Troubleshooting

### "Not connected to PyMOL"
- Ensure PyMOL is running
- Check plugin is started: `Plugin` → `agentic-pymol plugin` → `Server Status`
- Verify port: `lsof -i :9877`

### Plugin menu not appearing
- Restart PyMOL after installation
- Check PyMOL console for errors

### MCP server won't start
- Test manually: `python -m pymol_mcp.server`
- Check Python path in config
- Verify installation: `pip list | grep fastmcp`

## Next Steps

- Read [README.md](README.md) for full documentation
- See [PLUGIN_INSTALL.md](PLUGIN_INSTALL.md) for detailed plugin setup
- Check [MCP_CONFIG.md](MCP_CONFIG.md) for advanced configuration
- Run tests: `pytest tests/`
- Try integration test: `python tests/integration_test.py`

## Support

Open an issue on GitHub with:
- PyMOL version
- Python version
- Error messages
- Steps to reproduce

```

### DEVIN_TASKS.md

```markdown
# Devin Task Specs — NovoProteinAI

Paste these one at a time into Devin. Each is scoped to be completable independently.

---

## Task 1 — Fetch.ai research agent (the "research layer")

**Goal:** Build a Fetch.ai uAgent that takes a plain-English vaccine/therapeutic goal
and returns a structured target + epitope + known-binder result, sourced from public
biology databases. It must be ASI:One-discoverable and registered on Agentverse.

**Repo:** `github.com/santosrai/NovoProteinAI`. Add the agent as `research_agent.py`
at the repo root. Do NOT modify `visualize.py` — your output must match its inputs.

### Hard output contract (this is the whole point — match it exactly)
The agent must return a JSON object with these fields:
```json
{
  "target_name": "SARS-CoV-2 spike receptor-binding domain",
  "pdb_id": "6VXX",
  "chain": "A",
  "epitope_residues": [417, 484, 501],
  "binder_pdb_ids": ["7K8M"],
  "explanation": "Plain-English summary a non-biologist can follow.",
  "citations": [
    {"title": "...", "pmid": "...", "url": "https://pubmed.ncbi.nlm.nih.gov/..."}
  ]
}
```
`pdb_id` (str), `chain` (str), and `epitope_residues` (list[int]) are REQUIRED and
feed directly into `render_image(pdb_id, epitope_residues, chain, binder_pdb_id)` in
`visualize.py`. `binder_pdb_ids` may be an empty list. Validate that `pdb_id` actually
exists in the PDB before returning.

### Data sources (use real APIs, no hardcoding)
- **PubMed** (E-utilities): `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi`
  and `efetch.fcgi` — find relevant papers for the goal, return citations.
- **RCSB PDB** search + data API: `https://search.rcsb.org/rcsbsearch/v2/query` and
  `https://data.rcsb.org/rest/v1/core/entry/{id}` — resolve target → a real PDB ID,
  identify the relevant chain, and find known antibody/binder complexes.
- **IEDB**: epitope residue ranges for the target where available.

### Fetch.ai requirements (for the sponsor track)
- `pip install uagents` (add to `requirements.txt`).
- Implement with the **chat protocol** so the agent is ASI:One-compatible
  (see https://uagents.fetch.ai/docs/guides/chat_protocol).
- Register on **Agentverse** via mailbox so it's discoverable.
- Use **ASI:One** (Fetch.ai's LLM) to turn the messy goal string into good database
  search terms and to draft the `explanation` field. API key via env var `ASI_ONE_API_KEY`.
- Agent name: `novoprotein-research`. Use a fixed `seed` from env `AGENT_SEED`.

### Acceptance criteria
1. Running the agent and sending it `"build a vaccine for COVID"` returns a valid
   object matching the contract above, with a real `pdb_id`.
2. The returned `pdb_id`/`chain`/`epitope_residues` produce an image when passed to
   `render_image()` (test this end-to-end and include the resulting PNG in the PR).
3. Agent is registered on Agentverse and reachable via the chat protocol.
4. `requirements.txt` updated; a short `README` section documents how to run it and
   which env vars are needed.
5. Graceful hand
[truncated — 222 more characters]
```

### requirements.txt

```
fastmcp>=0.1.0
pyyaml>=6.0

# Research agent (Fetch.ai uAgent for Agentverse / ASI:One)
uagents>=0.12.0
uagents-core
requests>=2.31.0
python-dotenv>=1.0.0

# Agentic brain: Claude (Anthropic) + LangGraph + MCP tool loading
langgraph>=0.2.0
langchain-anthropic>=0.2.0
langchain-core>=0.3.0
langchain-mcp-adapters>=0.1.0

```

### pyproject.toml

```
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "pymol-mcp-bridge"
version = "0.1.0"
description = "MCP bridge exposing PyMOL as typed tools to LLM agents"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
    {name = "NovoProteinAI"}
]
dependencies = [
    "fastmcp>=0.1.0",
    "pyyaml>=6.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "pytest-asyncio>=0.21",
]

[project.scripts]
pymol-mcp = "pymol_mcp.server:main"

[tool.setuptools.packages.find]
include = ["pymol_mcp*"]
exclude = ["tests*", "pymol_plugin*"]

```

### pymol_mcp/server.py

```python
import sys
import logging
from typing import Optional
from fastmcp import FastMCP
from .config import PyMOLConfig
from .client import get_client
from . import tools

logger = logging.getLogger(__name__)

mcp = FastMCP("PyMOL MCP Bridge")


@mcp.tool()
def load_structure(source: str, object_name: Optional[str] = None) -> str:
    """
    Load a molecular structure into PyMOL.

    Opens local structure files (including your own non-PDB CIF files) or
    fetches a structure from the PDB by ID.

    Args:
        source: A local file path or a 4-character PDB ID. Supported local
            extensions (optionally gzipped): .pdb, .ent, .cif, .mmcif, .mcif,
            .mol2, .mol, .sdf, .xyz, .pdbqt, .mae. The file must exist on the
            machine where PyMOL is running.
        object_name: Optional name for the loaded object. If not provided, uses filename or PDB ID

    Returns:
        Success message with the object name
    """
    return tools.load_structure(source, object_name)


@mcp.tool()
def select_atoms(selection_name: str, selection_expr: str) -> str:
    """
    Create a named selection of atoms in PyMOL.
    
    Args:
        selection_name: Name for the new selection
        selection_expr: PyMOL selection expression (e.g., 'resn ALA', 'chain A and resi 1-10')
    
    Returns:
        Message with the number of atoms selected
    """
    return tools.select_atoms(selection_name, selection_expr)


@mcp.tool()
def color_selection(color: str, selection: str = "all") -> str:
    """
    Apply a color to a selection in PyMOL.
    
    Args:
        color: Color name (e.g., 'red', 'blue', 'green', 'cyan', 'magenta', 'yellow', 'orange')
        selection: Selection to color (default: 'all')
    
    Returns:
        Confirmation message
    """
    return tools.color_selection(color, selection)


@mcp.tool()
def rotate(axis: str = "y", angle: float = 90, selection: str = "") -> str:
    """
    Rotate the camera view or a specific object/selection in PyMOL.

    Args:
        axis: Rotation axis, one of 'x', 'y', or 'z' (default: 'y')
        angle: Rotation angle in degrees (default: 90)
        selection: Optional object/selection to rotate. If empty, rotates the
            camera view instead of the molecule (default: '')

    Returns:
        Confirmation message
    """
    return tools.rotate(axis, angle, selection)


@mcp.tool()
def render_image(
    output_path: str,
    width: int = 800,
    height: int = 600,
    ray_trace: bool = False
) -> str:
    """
    Render and save an image of the current PyMOL view.
    
    Args:
        output_path: Path where the PNG image will be saved
        width: Image width in pixels (default: 800)
        height: Image height in pixels (default: 600)
        ray_trace: Whether to use ray tracing for high-quality rendering (default: False)
    
    Returns:
        Path to the saved image
    """
    return tools.render_image(output_path, width, height, ray_trace)


@mcp.tool()
def ping_pymol() -> str:
    """
    Check connection to PyMOL and get version information.
    
    Returns:
        Connection status and PyMOL version
    """
    return tools.ping_pymol()


def main():
    """Main entry point for the MCP server."""
    import argparse
    
    parser = argparse.ArgumentParser(description="PyMOL MCP Bridge Server")
    parser.add_argument(
        "--config",
        type=str,
        help="Path to configuration file (YAML)"
    )
    parser.add_argument(
        "--host",
        type=str,
        help="PyMOL plugin host (overrides config)"
    )
    parser.add_argument(
        "--port",
        type=int,
        help="PyMOL plugin port (overrides config)"
    )
    
    args = parser.parse_args()
    
    config = PyMOLConfig.load(args.config)
    
    if args.host:
        config.host = args.host
    if args.port:
        config.port = args.port
    
    client = get_client(config)
    
    logger.info(f"Starting PyMOL MCP server (connecting to {config.host}:{config.port})")
    
    if not client.ping():
        logger.warning("Could not connect to PyMOL plugin. Make sure PyMOL is running and the plugin is started.")
        logger.warning("The server will start anyway and attempt to connect on first request.")
    else:
        logger.info("Successfully connected to PyMOL plugin")
    
    try:
        mcp.run()
    except KeyboardInterrupt:
        logger.info("Shutting down PyMOL MCP server")
        client.disconnect()
        sys.exit(0)
    except Exception as e:
        logger.error(f"Server error: {e}")
        client.disconnect()
        sys.exit(1)


if __name__ == "__main__":
    main()

```

### setup.sh

```shell
#!/bin/bash
# Setup script for PyMOL MCP Bridge

set -e

echo "============================================================"
echo "PyMOL MCP Bridge - Setup"
echo "============================================================"

# Check Python version
echo ""
echo "Checking Python..."
if ! command -v python3 &> /dev/null; then
    echo "❌ Python 3 not found. Please install Python 3.8 or higher."
    exit 1
fi

PYTHON_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
echo "✓ Python $PYTHON_VERSION found"

# Install dependencies
echo ""
echo "Installing dependencies..."
python3 -m pip install -e . || {
    echo "❌ Failed to install dependencies"
    echo "Try: python3 -m pip install --user -e ."
    exit 1
}

echo "✓ Dependencies installed"

# Verify installation
echo ""
echo "Verifying installation..."
python3 verify_install.py || {
    echo "⚠️  Verification found issues"
    exit 1
}

echo ""
echo "============================================================"
echo "Setup Complete!"
echo "============================================================"
echo ""
echo "Next steps:"
echo ""
echo "1. Install PyMOL Plugin:"
echo "   - Open PyMOL"
echo "   - Plugin → Plugin Manager → Install New Plugin"
echo "   - Select: pymol_plugin/__init__.py"
echo "   - Restart PyMOL"
echo ""
echo "2. Start PyMOL Plugin:"
echo "   - Plugin → agentic-pymol plugin → Start Listening"
echo ""
echo "3. Configure MCP Client:"
echo "   - See MCP_CONFIG.md for your client (Claude Desktop, Cline, etc.)"
echo ""
echo "4. Test Installation:"
echo "   python3 tests/integration_test.py"
echo ""
echo "For more information, see:"
echo "  - QUICKSTART.md - Quick start guide"
echo "  - README.md - Full documentation"
echo "  - PLUGIN_INSTALL.md - Plugin installation details"
echo ""

```

### verify_install.py

```python
#!/usr/bin/env python
"""
Verification script for PyMOL MCP Bridge installation.

This script checks that all components are properly installed and configured.

Usage:
    python verify_install.py
"""

import sys
import os
from pathlib import Path


def check_python_version():
    """Check Python version is >= 3.8."""
    print("Checking Python version...")
    version = sys.version_info
    if version.major >= 3 and version.minor >= 8:
        print(f"✓ Python {version.major}.{version.minor}.{version.micro}")
        return True
    else:
        print(f"❌ Python {version.major}.{version.minor}.{version.micro} (requires >= 3.8)")
        return False


def check_dependencies():
    """Check required dependencies are installed."""
    print("\nChecking dependencies...")
    
    dependencies = {
        "fastmcp": "FastMCP",
        "yaml": "PyYAML",
    }
    
    all_ok = True
    for module, name in dependencies.items():
        try:
            __import__(module)
            print(f"✓ {name} installed")
        except ImportError:
            print(f"❌ {name} not installed")
            all_ok = False
    
    return all_ok


def check_package_structure():
    """Check package structure is correct."""
    print("\nChecking package structure...")
    
    required_files = [
        "pymol_mcp/__init__.py",
        "pymol_mcp/server.py",
        "pymol_mcp/client.py",
        "pymol_mcp/config.py",
        "pymol_mcp/tools.py",
        "pymol_plugin/__init__.py",
        "requirements.txt",
        "pyproject.toml",
        "README.md",
    ]
    
    base_dir = Path(__file__).parent
    all_ok = True
    
    for file_path in required_files:
        full_path = base_dir / file_path
        if full_path.exists():
            print(f"✓ {file_path}")
        else:
            print(f"❌ {file_path} missing")
            all_ok = False
    
    return all_ok


def check_pymol_mcp_import():
    """Check pymol_mcp package can be imported."""
    print("\nChecking pymol_mcp package...")
    
    try:
        import pymol_mcp
        print(f"✓ pymol_mcp package importable (version {pymol_mcp.__version__})")
        
        from pymol_mcp import config, client, tools
        print("✓ All submodules importable")
        
        return True
    except ImportError as e:
        print(f"❌ Cannot import pymol_mcp: {e}")
        print("   Run: pip install -e .")
        return False


def check_server_command():
    """Check server can be invoked."""
    print("\nChecking server command...")
    
    try:
        from pymol_mcp.server import main
        print("✓ Server entry point accessible")
        return True
    except ImportError as e:
        print(f"❌ Cannot import server: {e}")
        return False


def check_plugin_file():
    """Check PyMOL plugin file exists and is valid."""
    print("\nChecking PyMOL plugin...")
    
    base_dir = Path(__file__).parent
    plugin_path = base_dir / "pymol_plugin" / "__init__.py"
    
    if not plugin_path.exists():
        print("❌ Plugin file not found")
        return False
    
    try:
        with open(plugin_path, 'r') as f:
            content = f.read()
            
        required_functions = [
            "__init_plugin__",
            "start_server",
            "stop_server",
            "show_status",
        ]
        
        all_ok = True
        for func in required_functions:
            if func in content:
                print(f"✓ Plugin has {func}()")
            else:
                print(f"❌ Plugin missing {func}()")
                all_ok = False
        
        return all_ok
    
    except Exception as e:
        print(f"❌ Error reading plugin: {e}")
        return False


def check_documentation():
    """Check documentation files exist."""
    print("\nChecking documentation...")
    
    docs = [
        "README.md",
        "QUICKSTART.md",
        "PLUGIN_INSTALL.md",
        "MCP_CONFIG.md",
        "PROJECT_SUMMARY.md",
    ]
    
    base_dir = Path(__file__).parent
    all_ok = True
    
    for doc in docs:
        if (base_dir / doc).exists():
            print(f"✓ {doc}")
        else:
            print(f"❌ {doc} missing")
            all_ok = False
    
    return all_ok


def check_tests():
    """Check test files exist."""
    print("\nChecking tests...")
    
    tests = [
        "tests/__init__.py",
        "tests/test_protocol.py",
        "tests/test_client.py",
        "tests/test_tools.py",
        "tests/integration_test.py",
    ]
    
    base_dir = Path(__file__).parent
    all_ok = True
    
    for test in tests:
        if (base_dir / test).exists():
            print(f"✓ {test}")
        else:
            print(f"❌ {test} missing")
            all_ok = False
    
    return all_ok


def main():
    """Run all verification checks."""
    print("=" * 60)
    print("PyMOL MCP Bridge - Installation Verification")
    print("=" * 60)
    
    checks = [
        ("Python Version", check_python_version),
        ("Dependencies", check_dependencies),
        ("Package Structure", check_package_structure),
        ("PyMOL MCP Import", check_pymol_mcp_import),
        ("Server Command", check_server_command),
        ("PyMOL Plugin", check_plugin_file),
        ("Documentation", check_documentation),
        ("Tests", check_tests),
    ]
    
    results = []
    for name, check_func in checks:
        try:
            success = check_func()
            results.append((name, success))
        except Exception as e:
            print(f"❌ {name} check failed with exception: {e}")
            results.append((name, False))
    
    print("\n" + "=" * 60)
    print("Verification Summary")
    print("=" * 60)
    
    passed = sum(1 for _, success in results if success)
    total = len(results)
    
    for name, success in results:
        status = "✓ PASS" if success else "❌ FAIL"
        print(f"{status}: {name}")
    
    print(f"\nTotal: {passed}/{total} checks passed")
    
    if passed == total:
  
[truncated — 654 more characters]
```

### tests/__init__.py

```python
"""Tests for PyMOL MCP Bridge."""

```

### src/__init__.py

```python
"""NovoProteinAI agent package."""

```

### pymol_mcp/__init__.py

```python
"""PyMOL MCP Bridge - Expose PyMOL as MCP tools to LLM agents."""

__version__ = "0.1.0"

from .config import PyMOLConfig
from .client import PyMOLClient, get_client
from . import tools

__all__ = [
    "PyMOLConfig",
    "PyMOLClient",
    "get_client",
    "tools",
]

```

[17 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]