# 星系形态学 MCP 工具

一个用于星系形态学分析的 MCP (Model Context Protocol) 服务器，封装了 GALFIT 和 GalfitS 工具，支持单波段和多波段星系图像拟合与分析。同时提供基于 FastAPI + Celery 的 HTTP 服务接口，支持异步任务提交。

## 功能特性

### 核心工具

| 工具名称 | 功能描述 | 可用条件 |
|---------|---------|---------|
| `run_galfit` | 执行 GALFIT 单波段拟合，返回优化的 FITS 文件、对比图像和拟合摘要 | 需设置 `GALFIT_BIN` |
| `run_galfits` | 执行 GalfitS 多波段同时拟合，返回摘要文件、图像、SED 模型等结果 | 需设置 `GALFITS_BIN` |
| `view_original_image` | 分析原始星系图像，提取形态分类和结构组件信息 | 要求提供2 panel图 |
| `component_analysis` | 分析拟合残差图像，诊断缺失或配置不当的物理组件（bulge、disk、bar、AGN 等）；多轮迭代中维护"最优轮次"登记并做轮间对比 | 始终可用 |

### 输出说明

**GALFIT 输出：**
- `optimized_fits_file`: 包含原始数据、模型和残差的 FITS 文件
- `image_file`: 2×3 科学对比图（行0：低/高动态范围原图 \| 模型；行1：全场残差/σ \| 残差放大 \| 1D 表面亮度剖面）
- `summary_file`: Markdown 格式的拟合参数摘要

**GalfitS 输出：**
- `summary_files`: `.gssummary` 拟合摘要文件
- `imagefit_pngs`: 多波段图像拟合对比图
- `sedmodel_pngs`: SED (光谱能量分布) 模型图
- `result_fits`: 最佳拟合 FITS 结果文件

### HTTP 服务接口

除 MCP 协议外，还提供基于 FastAPI + Celery 的 HTTP 服务，支持异步拟合任务提交：

- `POST /api/fitting/` — 提交拟合任务（支持 image fitting / pure sed fitting / image sed fitting 三种模式）
- `GET /api/fitting-status/{task_id}` — 查询任务状态
- `GET /health` — MCP 服务健康检查
- `GET /api/tools` — 列出当前可用的 MCP 工具

## 安装

### 环境要求

- Python >= 3.10
- GALFIT（用于单波段拟合，可选）
- GalfitS（用于多波段拟合，可选）

### 通过 pip 安装

```bash
pip install -e .
```

### Docker 部署

```bash
docker-compose up -d
```

服务包含两个容器：
- **fastapi**: Web 服务 (端口 8000)，负责任务提交和状态查询
- **celery**: 异步任务 Worker，执行后台拟合计算

## 配置

根据使用场景选择配置方式：

- **本地开发/直接运行**：创建 `.env` 文件（参考 `.env.example`），服务启动时自动加载。
- **MCP 客户端接入（如 Claude Code）**：优先在 `.mcp.json` 的 `env` 字段中配置环境变量，无需 `.env` 文件。

```bash
# LLM API 配置（用于多模态分析）
OPENAI_API_KEY=your_api_key_here
OPENAI_BASE_URL=           # 可选，默认使用官方端点
OPENAI_MODEL=gemini-3-flash-preview  # 推荐 gemini-3-flash-preview（性价比高）

# 分析后端选择：vlm（默认）、cc 或 acp
# vlm: 使用 OpenAI 兼容 API 进行分析（需配置 OPENAI_*）
# cc:  使用 Claude Code Agent SDK 进行分析（需配置 CLAUDECODE_*）
# acp: 使用 Gemini CLI ACP 模式进行分析（默认使用本地已登录会话）
ANALYSIS_MODE=vlm

# Claude Code Agent SDK 配置（ANALYSIS_MODE=cc 时需要）
# cc 模式通过 claude-agent-sdk 调用 Anthropic API
# 如需使用非 Anthropic 模型（如第三方 LLM），可安装 Claude Code Router 作为本地代理：
# https://github.com/musistudio/claude-code-router
CLAUDECODE_API_KEY=your_anthropic_api_key_here
CLAUDECODE_BASE_URL=xxx
CLAUDECODE_MODEL=gemini-3-flash-preview 

# Gemini CLI ACP 模式配置 (ANALYSIS_MODE=acp 时)
# 默认使用本地通过 `gemini login` 建立的会话。
# 如在 CI 等无登录环境，可配置 GEMINI_API_KEY。
# GEMINI_API_KEY=your_google_api_key_here
# GEMINI_MODEL=gemini-2.0-flash

# GALFIT 配置
GALFIT_BIN=/path/to/galfit  # GALFIT 可执行文件路径

# GalfitS 配置
GALFITS_BIN=/path/to/galfits       # GalfitS 命令或 Python 模块路径
GS_DATA_PATH=/path/to/gs_data      # GalfitS 数据目录

# HTTP 服务（可选）
MCP_ALLOWED_HOSTS=*                # 允许的主机，默认允许所有

# visualRAG 残差检索增强（可选，仅 ANALYSIS_MODE=vlm 时生效）
# 留空 VISUALRAG_SERVICE_URL 即关闭（静默降级为无参考样例的单图分析）
VISUALRAG_SERVICE_URL=http://127.0.0.1:8765   # 检索服务地址；空 = 关闭（唯一真正的开启开关）
VISUALRAG_ENABLED=1                # kill-switch，仅 =0 关闭（判据 != "0"，故 =false/no/off 不生效，须写 0）
VISUALRAG_TOP_K=5                  # 每个 role（baseline/positive/hard_negative）检索返回上限
VISUALRAG_STRATEGY=both            # 检索策略：both = DINOv2 视觉嵌入 + 参数化特征双特征
VISUALRAG_MIN_SCORE=0.3            # 质量门：服务端丢弃 score < 该值的样例；留空 = 不传，用服务端默认（当前 0.3）
```

## 使用方法

### 外部编排与轮次事件

本工程只提供 MCP 拟合与分析能力，不负责启动外部 Agent。平台侧的
`communication_service` 接收任务、拉起 Codex/Claude，并通过 stdio 挂载本 MCP。
`run_galfit` 和 `run_galfits_*` 的公开参数不包含通信字段。

编排服务可在启动 MCP 时设置任务级 `FIT_ROUND_EVENT_URL`。拟合成功后 tools 会发布标准
`fit_round_finished` 事件；普通 CLI 或独立 MCP 未配置该变量时自动使用空发布器，不产生网络请求。

### 启动 MCP 服务器

**STDIO 模式**（本地 MCP 客户端，如 Claude Code）：
```bash
python -m mcp_server --transport stdio
```

**HTTP 模式**（远程网络访问）：
```bash
python -m mcp_server --transport http --port 38507
```

支持的启动参数：

| 参数 | 说明 | 默认值 |
|------|------|--------|
| `--transport, -t` | 传输模式：stdio 或 http | stdio |
| `--host, -H` | HTTP 监听地址 | 0.0.0.0 |
| `--port, -p` | HTTP 监听端口 | 38507 |
| `--path, -P` | MCP 协议路径 | /mcp |

### 配置 Claude Code

在项目的 `.mcp.json` 中添加：

```json
{
  "mcpServers": {
    "galmcp": {
      "command": "python",
      "args": [
        "src/mcp_server.py",
        "--transport",
        "stdio"
      ],
      "env": {
        "GALFIT_BIN": "/usr/bin/galfit",
        "GALFITS_BIN": "python /path/to/GalfitS/src/galfits/galfitS.py",
        "GS_DATA_PATH": "/path/to/GalfitS",
        "OPENAI_API_KEY": "your-apikey",
        "OPENAI_BASE_URL": "https://open.bigmodel.cn/api/coding/paas/v4",
        "OPENAI_MODEL": "glm-4.6v",
        "ANALYSIS_MODE": "vlm",
        "CLAUDECODE_API_KEY": "your-anthropic-apikey",
        "CLAUDECODE_BASE_URL": "",
        "CLAUDECODE_MODEL": "gemini-3-flash-preview"
      }
    }
  }
}
```

> **分析模式说明：** `ANALYSIS_MODE` 控制残差分析（`component_analysis`）的后端：
> - `vlm`（默认）：通过 OpenAI 兼容 API 调用多模态模型，需配置 `OPENAI_API_KEY`、`OPENAI_BASE_URL`、`OPENAI_MODEL`。
> - `cc`：通过 Claude Code Agent SDK 调用 Anthropic API，需配置 `CLAUDECODE_API_KEY`。支持通过 [Claude Code Router](https://github.com/musistudio/claude-code-router) 代理到其他 LLM 提供商。
> - `acp`：通过 Gemini CLI ACP 模式调用分析，默认使用 `gemini login` 后的本地会话。

## visualRAG 检索增强（可选）

visualRAG 是一个**在线残差检索服务**，为 `component_analysis` 的 `vlm` 分析模式提供 Few-shot 参考样例。开启后，分析的第 1 轮（turn-1）会在目标星系图之前注入若干「参考样例图 + 专家图注」（按顺序：基线样例 / 困难反例 / 正样例），帮助多模态模型校准「视觉特征 → 诊断 → 处方」的判别规则；最后一张图仍是本轮待分析的目标星系。

> 仅在 `ANALYSIS_MODE=vlm` 时生效；`cc` / `acp` 模式不调用。客户端实现见 `src/tools/visualrag_client.py`。

### 触发与数据流

每次 `component_analysis`（vlm 模式）执行 turn-1 前，会调用 `_maybe_fetch_reference_blocks`：

1. 从 comparison PNG 路径反推 GALFIT archive 目录（以 `galfit.[0-9]*` 为标记）。
2. 收集本轮原料打成一个自包含 zip 上传（文件路径引用改写为 basename）：
   - feedme、**模型输出 cube**（`*_galfit.fits`）、mask、sigma、最新的 `galfit.NN`。
   - **不上传原始科学图像**——服务端只依据模型 cube + mask + sigma + feedme 抽特征。
3. POST 到 `{VISUALRAG_SERVICE_URL}/query`，服务端用 **DINOv2 视觉嵌入 + 主导成分参数化特征**（Re / n / b/a / mag）做 FAISS 检索。
4. 返回 JSON，含 `baseline`（基线）、`positive`（正样例）、`hard_negatives`（困难反例）、`perfect`、`query`（查询特征）、`warnings`。
5. 按 Few-shot 顺序 `[baseline → hard_negative → positive]` 下载各样例对比 PNG 到临时文件，拼到 turn-1 图像序列最前面；分析结束后清理临时文件。

整个过程是 **best-effort**：服务关闭 / 不可用 / 返回空 → 静默降级为普通单图 turn-1（无参考样例），不影响分析流程。

### 环境变量

| 变量 | 默认值 | 说明 |
|------|--------|------|
| `VISUALRAG_SERVICE_URL` | `""`（空） | 检索服务地址（如 `http://127.0.0.1:8765`）。**空 = 检索关闭**，是唯一真正的开启开关。 |
| `VISUALRAG_ENABLED` | `"1"` | 显式 kill-switch。仅 `=0` 关闭；判据是 `!= "0"`，故 `=false`/`=no`/`=off` **不会**关闭（须写 `0`）。 |
| `VISUALRAG_TOP_K` | `5` | 每个 role 检索返回上限。 |
| `VISUALRAG_STRATEGY` | `both` | 检索策略：`both` = 视觉 + 参数化双特征。 |
| `VISUALRAG_MIN_SCORE` | 空（用 server 默认 `0.3`） | 检索质量下限：服务端丢弃 `score < min_score` 的样例（被丢弃的正样例会连带丢掉它的 hard-neg）。**留空 = 不传该字段，由服务端 config 默认决定**（当前 `0.3`）。 |

配置方式与其它变量一致（`.env` 或 `.mcp.json` 的 `env` 字段）。注意 `query_service` 对 `top_k`/`strategy` 的回退顺序是「显式参数 → 环境变量 → 默认值」，且用 `or` 判断，因此传 `0`/空串会被当成未传而回退到默认值。`min_score` 不同：用 `is None` 判断，空串/未设 = 不传该字段（服务端走其默认 `0.3`），而显式 `0` 会被如实发送（含义为「丢弃负分样例」）。服务端库规模、模型、当前 `min_score` 默认等可通过 `GET {VISUALRAG_SERVICE_URL}/health` 查看。


## 最优轮次登记与轮间对比（best-round registry）

`component_analysis` 在多轮迭代拟合中会维护一个**最优轮次登记表**（`src/tools/best_round_registry.py`），按星系主目录索引，记录当前星系的历史最优轮次及其成分参数、卡方、component_analysis 结论。

- 每轮分析前，若存在历史最优且与当前轮不同，会自动生成一份**轮间对比**（`run_round_comparison`）：把历史最优轮与当前轮的对比图交给多模态模型判定 `CURRENT_BETTER / HISTORICAL_BETTER / EQUAL`；判定退步（`HISTORICAL_BETTER`）时额外输出结构化字段 `regression_focus / salvage / direction(REVERT|AUGMENT)`，指明下一轮应回到历史最优轮起点重拟（`REVERT`），还是在当前方向上补全缺失要素（`AUGMENT`）。
- 退步结论作为**软参考**注入 `component_analysis` 的参数审查阶段（turn-2），不干预 turn-1 的视觉特征提取。
- 登记状态默认持久化到星系主目录下的 `.best_round.json`（已加入 `.gitignore`），便于跨会话恢复；设置环境变量 `BEST_ROUND_PERSIST=0` 可关闭，退化为纯内存登记。

### 落锁前强制审计（best-round-verifier）

正式锁定"最优轮次"之前，工作流（`workflow_galfit` / `workflow_galfits`）的阶段三会调用只读 subagent **`best-round-verifier`**（定义见 `.claude/agents/best-round-verifier.md`），对候选轮按**成分 / 拟合 / 物理 / 参数 / 校验 / 指标**六个维度做独立、机械、可追溯的审计，返回 `PASS | FAIL`：

- `FAIL` → 严禁落锁，按"阻断性问题"清单修复后重拟、复审至 `PASS`；
- `PASS`（含 `WARN`）→ 方可落锁。

审计细则与工作流约束详见 `AGENTS.md`。

## 项目结构

```
src/
├── mcp_server.py          # MCP 服务主入口，工具注册与传输配置
├── tools/
│   ├── run_galfit.py      # GALFIT 单波段拟合执行
│   ├── run_galfits.py     # GalfitS 多波段拟合执行
│   ├── analyze_image.py   # VLM 多模态分析（GALFIT/GalfitS 结果）
│   ├── view_original_image.py  # 原始星系图像形态分类
│   ├── component_analysis.py   # 残差分析与组件诊断
│   ├── best_round_registry.py  # 最优轮次登记与轮间对比（持久化 .best_round.json）
│   ├── visualrag_client.py     # visualRAG Few-shot 检索客户端（vlm 模式）
│   ├── modify_feedme.py   # GALFIT feedme 配置文件修改
│   ├── extract_summary_galfit.py  # GALFIT 参数摘要提取
│   ├── pix2radec.py       # 像素坐标转赤经赤纬
│   ├── read_fits.py       # FITS 文件读取工具
│   ├── multi_thresh_plot.py  # 多阈值可视化
│   └── prompt.py          # 工作流 Prompt 定义
├── service/
│   ├── main.py            # FastAPI 应用，HTTP 任务提交接口
│   ├── tasks.py           # Celery 异步任务定义
│   └── file_manager.py    # 文件与工作空间管理
├── llms/
│   ├── base.py            # LLM 客户端基类
│   ├── openai_llm.py      # OpenAI API 客户端
│   └── glm_llm.py         # 智谱 GLM API 客户端
└── prompts/               # Prompt 模板（分类、分析、工作流）
```


## 许可证

MIT License

## 贡献

欢迎提交 Issue 和 Pull Request！
