Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 836836596f | |||
| f9d266d3ab |
@@ -1,27 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [1.31.5] — 2026-04-22
|
||||
|
||||
- feat: add check_update and update tools with background auto-check
|
||||
- release: v1.31.4
|
||||
- fix: update all scripts for correctness and consistency
|
||||
- release: v1.31.3
|
||||
- docs: clean up public repo — remove IDE files, fix stale refs
|
||||
- release: v1.31.2
|
||||
- refactor: reduce handler boilerplate in analysis and experts modules
|
||||
- fix: registryType mcpbPackageType → mcpb
|
||||
|
||||
|
||||
## [1.31.4] — 2026-04-22
|
||||
|
||||
- fix: update all scripts for correctness and consistency
|
||||
- release: v1.31.3
|
||||
- docs: clean up public repo — remove IDE files, fix stale refs
|
||||
- release: v1.31.2
|
||||
- refactor: reduce handler boilerplate in analysis and experts modules
|
||||
- fix: registryType mcpbPackageType → mcpb
|
||||
|
||||
|
||||
## [1.31.3] — 2026-04-22
|
||||
|
||||
- docs: clean up public repo — remove IDE files, fix stale refs
|
||||
|
||||
Generated
+1
-1
@@ -481,7 +481,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mt5-quant"
|
||||
version = "1.31.5"
|
||||
version = "1.31.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mt5-quant"
|
||||
version = "1.31.5"
|
||||
version = "1.31.3"
|
||||
edition = "2021"
|
||||
description = "MT5-Quant MCP Server - Exposes MT5 backtest and optimization tools via MCP"
|
||||
authors = ["masdevid <masdevid@example.com>"]
|
||||
|
||||
@@ -67,7 +67,7 @@ The AI runs the full pipeline: compile → clean cache → backtest → extract
|
||||
| [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) | Common issues |
|
||||
| [REMOTE_AGENTS.md](docs/REMOTE_AGENTS.md) | Linux optimization agents |
|
||||
|
||||
## MCP Tools (89)
|
||||
## MCP Tools (87)
|
||||
|
||||
### Core workflow
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
Full input/output schemas for MT5-Quant tools.
|
||||
|
||||
> **Documentation Status:** This file documents 56 of 89 total tools. Missing:
|
||||
> **Documentation Status:** This file documents 56 of 87 total tools. Missing:
|
||||
> - `list_experts`, `list_indicators`, `list_scripts`
|
||||
> - `healthcheck`, `list_symbols`
|
||||
> - Reports query: `search_reports`, `get_latest_report`, `list_reports`, `prune_reports`, `tail_log`, `get_report_by_id`, `get_reports_summary`, `get_best_reports`, `search_reports_by_tags`, `search_reports_by_date_range`, `search_reports_by_notes`, `get_reports_by_set_file`, `get_comparable_reports`
|
||||
|
||||
+23
-30
@@ -1,11 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build release binary and package it for distribution
|
||||
# Creates: dist/mcp-mt5-quant-{platform}.tar.gz
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/build-release.sh
|
||||
#!/bin/bash
|
||||
# Build release binaries for distribution
|
||||
# Creates: dist/mt5-quant-{platform}.tar.gz
|
||||
|
||||
set -euo pipefail
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
@@ -13,17 +10,16 @@ PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
VERSION=$(grep -E '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
|
||||
echo "=== Building mt5-quant v${VERSION} ==="
|
||||
echo "=== Building MCP-MT5-Quant v${VERSION} ==="
|
||||
echo ""
|
||||
|
||||
# Clean previous builds
|
||||
rm -rf "$PROJECT_ROOT/dist"
|
||||
mkdir -p "$PROJECT_ROOT/dist"
|
||||
|
||||
# Build release binary
|
||||
echo "Building release binary..."
|
||||
# Build current platform
|
||||
echo "Building for current platform..."
|
||||
RUSTFLAGS="-D warnings" cargo build --release
|
||||
echo ""
|
||||
|
||||
# Detect platform
|
||||
UNAME=$(uname -s)
|
||||
@@ -34,46 +30,43 @@ if [[ "$UNAME" == "Darwin" ]]; then
|
||||
elif [[ "$UNAME" == "Linux" ]]; then
|
||||
PLATFORM="linux-${ARCH}"
|
||||
else
|
||||
PLATFORM="unknown-${ARCH}"
|
||||
PLATFORM="unknown"
|
||||
fi
|
||||
|
||||
PACKAGE_NAME="mcp-mt5-quant-${PLATFORM}"
|
||||
PACKAGE_DIR="$PROJECT_ROOT/dist/${PACKAGE_NAME}"
|
||||
|
||||
echo "Packaging for ${PLATFORM}..."
|
||||
mkdir -p "$PACKAGE_DIR/docs"
|
||||
mkdir -p "$PACKAGE_DIR"
|
||||
|
||||
# Binary
|
||||
# Copy binary
|
||||
cp "$PROJECT_ROOT/target/release/mt5-quant" "$PACKAGE_DIR/"
|
||||
chmod +x "$PACKAGE_DIR/mt5-quant"
|
||||
|
||||
# Config template
|
||||
# Copy config template
|
||||
mkdir -p "$PACKAGE_DIR/config"
|
||||
cp "$PROJECT_ROOT/config/mt5-quant.example.yaml" "$PACKAGE_DIR/config/"
|
||||
|
||||
# Documentation
|
||||
cp "$PROJECT_ROOT/README.md" "$PACKAGE_DIR/"
|
||||
cp "$PROJECT_ROOT/docs/QUICKSTART.md" "$PACKAGE_DIR/docs/"
|
||||
cp "$PROJECT_ROOT/docs/CLAUDE.md" "$PACKAGE_DIR/docs/"
|
||||
cp "$PROJECT_ROOT/docs/CURSOR.md" "$PACKAGE_DIR/docs/"
|
||||
cp "$PROJECT_ROOT/docs/VSCODE.md" "$PACKAGE_DIR/docs/"
|
||||
cp "$PROJECT_ROOT/docs/WINDSURF.md" "$PACKAGE_DIR/docs/"
|
||||
# Copy docs
|
||||
cp "$PROJECT_ROOT/README.md" "$PACKAGE_DIR/"
|
||||
cp "$PROJECT_ROOT/docs/WINDSURF.md" "$PACKAGE_DIR/WINDSURF_SETUP.md"
|
||||
cp "$PROJECT_ROOT/CLAUDE.md" "$PACKAGE_DIR/"
|
||||
|
||||
# Create tarball
|
||||
cd "$PROJECT_ROOT/dist"
|
||||
tar -czf "${PACKAGE_NAME}.tar.gz" "$PACKAGE_NAME"
|
||||
rm -rf "$PACKAGE_NAME"
|
||||
|
||||
echo ""
|
||||
echo "=== Build complete ==="
|
||||
echo "=== Build Complete ==="
|
||||
echo ""
|
||||
echo "Package : dist/${PACKAGE_NAME}.tar.gz"
|
||||
echo "Size : $(du -h "${PACKAGE_NAME}.tar.gz" | cut -f1)"
|
||||
echo "Package: dist/${PACKAGE_NAME}.tar.gz"
|
||||
echo "Binary: mt5-quant"
|
||||
echo "Size: $(du -h "${PACKAGE_NAME}.tar.gz" | cut -f1)"
|
||||
echo ""
|
||||
echo "Contents:"
|
||||
tar -tzf "${PACKAGE_NAME}.tar.gz"
|
||||
tar -tzf "${PACKAGE_NAME}.tar.gz" | head -10
|
||||
echo ""
|
||||
echo "Install:"
|
||||
echo "To install:"
|
||||
echo " tar -xzf ${PACKAGE_NAME}.tar.gz"
|
||||
echo " sudo cp ${PACKAGE_NAME}/mt5-quant /usr/local/bin/"
|
||||
echo " cd ${PACKAGE_NAME}"
|
||||
echo " sudo cp mt5-quant /usr/local/bin/"
|
||||
echo " mt5-quant --help"
|
||||
|
||||
+15
-8
@@ -1,25 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build MT5-Quant release binary
|
||||
#!/bin/bash
|
||||
# Build MT5-Quant Rust MCP Server
|
||||
# Output: target/release/mt5-quant
|
||||
|
||||
set -euo pipefail
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
echo "=== MT5-Quant build ==="
|
||||
echo "Root: $PROJECT_ROOT"
|
||||
echo "=== MT5-Quant Rust Build ==="
|
||||
echo "Project root: $PROJECT_ROOT"
|
||||
echo ""
|
||||
|
||||
echo "Building release binary..."
|
||||
cargo build --release
|
||||
|
||||
echo ""
|
||||
echo "=== Done ==="
|
||||
echo "=== Build Complete ==="
|
||||
echo ""
|
||||
echo "Executable location:"
|
||||
ls -lh "$PROJECT_ROOT/target/release/mt5-quant"
|
||||
|
||||
echo ""
|
||||
echo "Run: ./target/release/mt5-quant --help"
|
||||
echo "Install: cargo install --path . --force"
|
||||
echo "To test:"
|
||||
echo " ./target/release/mt5-quant --help"
|
||||
echo ""
|
||||
echo "To install for Windsurf:"
|
||||
echo " Update ~/.windsurf/config.yaml:"
|
||||
echo " command: $PROJECT_ROOT/target/release/mt5-quant"
|
||||
echo ""
|
||||
|
||||
+38
-76
@@ -7,7 +7,6 @@
|
||||
# bash scripts/release.sh major # 1.31.0 → 2.0.0
|
||||
# bash scripts/release.sh 1.32.0 # explicit version
|
||||
# bash scripts/release.sh v1.32.0 # with v prefix
|
||||
# bash scripts/release.sh patch --yes # non-interactive (CI / Claude Code)
|
||||
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
@@ -19,54 +18,44 @@ warn() { echo -e "${YELLOW}⚠ $*${NC}"; }
|
||||
die() { echo -e "${RED}✗ $*${NC}" >&2; exit 1; }
|
||||
hr() { echo -e "${BLUE}────────────────────────────────────────${NC}"; }
|
||||
|
||||
# ── Argument parsing ───────────────────────────────────────────────────────────
|
||||
# ── Prerequisites ─────────────────────────────────────────────────────────────
|
||||
|
||||
bump="${1:-patch}"
|
||||
AUTO_YES=false
|
||||
if [[ "${2:-}" == "--yes" || "${2:-}" == "-y" || "${CI:-}" == "true" ]]; then
|
||||
AUTO_YES=true
|
||||
fi
|
||||
|
||||
# ── Prerequisites ──────────────────────────────────────────────────────────────
|
||||
|
||||
command -v cargo >/dev/null 2>&1 || die "cargo not found"
|
||||
command -v cargo >/dev/null 2>&1 || die "cargo not found"
|
||||
command -v python3 >/dev/null 2>&1 || die "python3 not found"
|
||||
|
||||
# ── Current version ────────────────────────────────────────────────────────────
|
||||
# ── Current version ───────────────────────────────────────────────────────────
|
||||
|
||||
current=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
|
||||
[[ -n "$current" ]] || die "Could not parse version from Cargo.toml"
|
||||
info "Current version: ${BOLD}$current${NC}"
|
||||
|
||||
# ── Compute new version ────────────────────────────────────────────────────────
|
||||
# ── Compute new version ───────────────────────────────────────────────────────
|
||||
|
||||
bump="${1:-patch}"
|
||||
IFS='.' read -r major minor patch_v <<< "$current"
|
||||
|
||||
case "$bump" in
|
||||
major) new="$((major + 1)).0.0" ;;
|
||||
minor) new="${major}.$((minor + 1)).0" ;;
|
||||
patch) new="${major}.${minor}.$((patch_v + 1))" ;;
|
||||
v*.*.*) new="${bump#v}" ;;
|
||||
*.*.*) new="$bump" ;;
|
||||
*) die "Usage: $0 [patch|minor|major|X.Y.Z] [--yes]" ;;
|
||||
major) new="${major+1}.0.0"; new="$((major + 1)).0.0" ;;
|
||||
minor) new="${major}.$((minor + 1)).0" ;;
|
||||
patch) new="${major}.${minor}.$((patch_v + 1))" ;;
|
||||
v*.*.*) new="${bump#v}" ;;
|
||||
*.*.*) new="$bump" ;;
|
||||
*) die "Usage: $0 [patch|minor|major|X.Y.Z]" ;;
|
||||
esac
|
||||
|
||||
# Validate semver
|
||||
[[ "$new" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "Invalid version: $new"
|
||||
|
||||
hr
|
||||
info "Releasing: ${BOLD}$current → $new${NC}"
|
||||
hr
|
||||
|
||||
# ── Confirm ────────────────────────────────────────────────────────────────────
|
||||
# ── Confirm ───────────────────────────────────────────────────────────────────
|
||||
|
||||
if [[ "$AUTO_YES" == false ]]; then
|
||||
read -rp "Proceed with release v${new}? [y/N] " confirm
|
||||
[[ "$confirm" =~ ^[Yy]$ ]] || die "Aborted"
|
||||
else
|
||||
info "Auto-confirmed (--yes)"
|
||||
fi
|
||||
read -rp "Proceed with release v${new}? [y/N] " confirm
|
||||
[[ "$confirm" =~ ^[Yy]$ ]] || die "Aborted"
|
||||
|
||||
# ── Check git state ────────────────────────────────────────────────────────────
|
||||
# ── Check git state ───────────────────────────────────────────────────────────
|
||||
|
||||
info "Checking git state..."
|
||||
if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
|
||||
@@ -75,12 +64,13 @@ fi
|
||||
current_branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
info "Branch: $current_branch"
|
||||
|
||||
# Check tag doesn't already exist
|
||||
if git rev-parse "v${new}" >/dev/null 2>&1; then
|
||||
die "Tag v${new} already exists"
|
||||
fi
|
||||
ok "Git state clean"
|
||||
|
||||
# ── 1. Bump Cargo.toml ─────────────────────────────────────────────────────────
|
||||
# ── 1. Bump Cargo.toml ────────────────────────────────────────────────────────
|
||||
|
||||
info "Bumping Cargo.toml..."
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
@@ -88,14 +78,16 @@ if [[ "$(uname)" == "Darwin" ]]; then
|
||||
else
|
||||
sed -i "s/^version = \"${current}\"/version = \"${new}\"/" Cargo.toml
|
||||
fi
|
||||
|
||||
# Update Cargo.lock
|
||||
cargo metadata --no-deps --format-version 1 >/dev/null 2>&1 || true
|
||||
ok "Cargo.toml → $new"
|
||||
|
||||
# ── 2. Update server.json ──────────────────────────────────────────────────────
|
||||
# ── 2. Update server.json ─────────────────────────────────────────────────────
|
||||
|
||||
info "Updating server.json..."
|
||||
NEW_VERSION="$new" python3 - <<'PYEOF'
|
||||
import json, os, re
|
||||
import json, os, re, sys
|
||||
|
||||
version = os.environ['NEW_VERSION']
|
||||
|
||||
@@ -106,6 +98,7 @@ data['version'] = version
|
||||
|
||||
for pkg in data.get('packages', []):
|
||||
pkg['version'] = version
|
||||
# Update download URL to point at new version tag
|
||||
if 'identifier' in pkg:
|
||||
pkg['identifier'] = re.sub(
|
||||
r'/v[0-9]+\.[0-9]+\.[0-9]+/',
|
||||
@@ -121,9 +114,9 @@ with open('server.json', 'w') as f:
|
||||
|
||||
print(f" server.json version={version}, identifier URL updated")
|
||||
PYEOF
|
||||
ok "server.json → $new (SHA256 set by CI)"
|
||||
ok "server.json → $new (SHA256 updated by CI)"
|
||||
|
||||
# ── 3. Generate CHANGELOG entry ────────────────────────────────────────────────
|
||||
# ── 3. Generate CHANGELOG entry ───────────────────────────────────────────────
|
||||
|
||||
info "Generating CHANGELOG entry..."
|
||||
prev_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
@@ -145,6 +138,7 @@ today=$(date +%Y-%m-%d)
|
||||
} > /tmp/release_entry.md
|
||||
|
||||
if [[ -f CHANGELOG.md ]]; then
|
||||
# Insert after the first header line
|
||||
tmp=$(mktemp)
|
||||
head -1 CHANGELOG.md > "$tmp"
|
||||
echo "" >> "$tmp"
|
||||
@@ -152,18 +146,22 @@ if [[ -f CHANGELOG.md ]]; then
|
||||
tail -n +2 CHANGELOG.md >> "$tmp"
|
||||
mv "$tmp" CHANGELOG.md
|
||||
else
|
||||
{ echo "# Changelog"; echo ""; cat /tmp/release_entry.md; } > CHANGELOG.md
|
||||
{
|
||||
echo "# Changelog"
|
||||
echo ""
|
||||
cat /tmp/release_entry.md
|
||||
} > CHANGELOG.md
|
||||
fi
|
||||
rm -f /tmp/release_entry.md
|
||||
ok "CHANGELOG.md updated"
|
||||
|
||||
# ── 4. Verify build ────────────────────────────────────────────────────────────
|
||||
# ── 4. Verify build compiles ──────────────────────────────────────────────────
|
||||
|
||||
info "Verifying build (cargo check)..."
|
||||
cargo check --quiet 2>&1 || die "cargo check failed — fix errors before releasing"
|
||||
ok "Build check passed"
|
||||
|
||||
# ── 5. Commit ──────────────────────────────────────────────────────────────────
|
||||
# ── 5. Commit ─────────────────────────────────────────────────────────────────
|
||||
|
||||
info "Creating release commit..."
|
||||
git add Cargo.toml Cargo.lock server.json CHANGELOG.md
|
||||
@@ -174,56 +172,20 @@ git commit -m "release: v${new}
|
||||
- CHANGELOG.md updated"
|
||||
ok "Release commit created"
|
||||
|
||||
# ── 6. Tag ─────────────────────────────────────────────────────────────────────
|
||||
# ── 6. Tag ────────────────────────────────────────────────────────────────────
|
||||
|
||||
info "Creating annotated tag v${new}..."
|
||||
git tag -a "v${new}" -m "Release v${new}"
|
||||
ok "Tagged v${new}"
|
||||
|
||||
# ── 7. Push (rebase if remote has new commits from CI) ─────────────────────────
|
||||
# ── 7. Push ───────────────────────────────────────────────────────────────────
|
||||
|
||||
info "Pushing to GitHub..."
|
||||
if ! git push origin "$current_branch" 2>/dev/null; then
|
||||
warn "Push rejected — rebasing on remote (CI may have committed SHA256)..."
|
||||
git fetch origin "$current_branch"
|
||||
|
||||
# Resolve server.json conflict automatically: keep our version
|
||||
if ! git rebase "origin/${current_branch}"; then
|
||||
if git diff --name-only --diff-filter=U | grep -q "server.json"; then
|
||||
python3 - <<'PYEOF'
|
||||
import re
|
||||
with open('server.json') as f:
|
||||
raw = f.read()
|
||||
resolved = re.sub(
|
||||
r'<<<<<<< HEAD.*?=======\n(.*?)>>>>>>> [^\n]+\n',
|
||||
r'\1',
|
||||
raw,
|
||||
flags=re.DOTALL
|
||||
)
|
||||
with open('server.json', 'w') as f:
|
||||
f.write(resolved)
|
||||
PYEOF
|
||||
git add server.json
|
||||
GIT_EDITOR=true git rebase --continue
|
||||
else
|
||||
git rebase --abort
|
||||
die "Rebase failed with unexpected conflicts — push manually"
|
||||
fi
|
||||
fi
|
||||
|
||||
git push origin "$current_branch"
|
||||
fi
|
||||
|
||||
# Push tag (delete and re-push if rebase moved the commit)
|
||||
git push origin "v${new}" 2>/dev/null || {
|
||||
git push origin ":refs/tags/v${new}" 2>/dev/null || true
|
||||
git tag -f -a "v${new}" -m "Release v${new}"
|
||||
git push origin "v${new}"
|
||||
}
|
||||
|
||||
git push origin "$current_branch"
|
||||
git push origin "v${new}"
|
||||
ok "Pushed — GitHub Actions triggered"
|
||||
|
||||
# ── Done ───────────────────────────────────────────────────────────────────────
|
||||
# ── Done ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
hr
|
||||
echo ""
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@
|
||||
# - Windsurf : ~/.codeium/windsurf/mcp_config.json (JSON, mcpServers)
|
||||
# - Cursor : ~/.cursor/mcp.json (JSON, mcpServers)
|
||||
# - VS Code : .vscode/mcp.json (JSON, servers - not mcpServers)
|
||||
# - Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json
|
||||
# - Antigravity : mcp_config.json via UI (JSON, mcpServers)
|
||||
#
|
||||
# Previous installations are auto-detected and uninstalled before re-registering.
|
||||
|
||||
@@ -1201,7 +1201,7 @@ _register_all_mcp_platforms() {
|
||||
echo " - Windsurf: Edit ~/.codeium/windsurf/mcp_config.json"
|
||||
echo " - Cursor: Edit ~/.cursor/mcp.json"
|
||||
echo " - VS Code: Edit .vscode/mcp.json (workspace) or use MCP: Add Server command"
|
||||
echo " - Claude Desktop: Edit ~/Library/Application Support/Claude/claude_desktop_config.json"
|
||||
echo " - Antigravity:Use Agent panel → MCP Servers → Manage → Edit configuration"
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
+3
-3
@@ -7,12 +7,12 @@
|
||||
"url": "https://github.com/masdevid/mt5-quant",
|
||||
"source": "github"
|
||||
},
|
||||
"version": "1.31.5",
|
||||
"version": "1.31.3",
|
||||
"packages": [
|
||||
{
|
||||
"registryType": "mcpb",
|
||||
"version": "1.31.5",
|
||||
"identifier": "https://github.com/masdevid/mt5-quant/releases/download/v1.31.5/mcp-mt5-quant-macos-arm64.tar.gz",
|
||||
"version": "1.31.3",
|
||||
"identifier": "https://github.com/masdevid/mt5-quant/releases/download/v1.31.3/mcp-mt5-quant-macos-arm64.tar.gz",
|
||||
"fileSha256": "TBD_CI_WILL_UPDATE",
|
||||
"transport": {
|
||||
"type": "stdio"
|
||||
|
||||
@@ -63,8 +63,6 @@ pub fn get_tools_list() -> Value {
|
||||
system::tool_list_symbols(),
|
||||
system::tool_healthcheck(),
|
||||
system::tool_get_active_account(),
|
||||
system::tool_check_update(),
|
||||
system::tool_update(),
|
||||
// Utility (8 tools)
|
||||
utility::tool_check_symbol_data_status(),
|
||||
utility::tool_get_backtest_history(),
|
||||
|
||||
@@ -45,25 +45,3 @@ pub fn tool_get_active_account() -> Value {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn tool_check_update() -> Value {
|
||||
json!({
|
||||
"name": "check_update",
|
||||
"description": "Check if a newer version of mt5-quant is available on GitHub. A background check runs automatically on the first tool call of each session; this tool returns that cached result instantly or fetches it on demand.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn tool_update() -> Value {
|
||||
json!({
|
||||
"name": "update",
|
||||
"description": "Download and install the latest mt5-quant binary from GitHub Releases, then replace the current executable in place. Restart the MCP connection after updating to load the new version.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ use anyhow::Result;
|
||||
use chrono::Datelike;
|
||||
use serde_json::{json, Value};
|
||||
use std::path::Path;
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use crate::models::Config;
|
||||
|
||||
mod system;
|
||||
@@ -15,13 +13,6 @@ mod setfiles;
|
||||
mod reports;
|
||||
mod utility;
|
||||
|
||||
/// Cached result of the background update check.
|
||||
/// - Not yet initialized: check still in flight (or not spawned yet)
|
||||
/// - Some(version): a newer version is available
|
||||
/// - None: already on latest, or GitHub unreachable
|
||||
pub(crate) static LATEST_VERSION: OnceLock<Option<String>> = OnceLock::new();
|
||||
static BACKGROUND_CHECK_SPAWNED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ToolHandler {
|
||||
pub config: Config,
|
||||
@@ -33,22 +24,12 @@ impl ToolHandler {
|
||||
}
|
||||
|
||||
pub async fn handle(&self, name: &str, args: &Value) -> Result<Value> {
|
||||
// Spawn a one-shot background update check on the very first tool call of the session.
|
||||
if !BACKGROUND_CHECK_SPAWNED.swap(true, Ordering::Relaxed) {
|
||||
tokio::spawn(async {
|
||||
let result = system::fetch_latest_version().await;
|
||||
let _ = LATEST_VERSION.set(result);
|
||||
});
|
||||
}
|
||||
|
||||
match name {
|
||||
// System handlers
|
||||
"verify_setup" => system::handle_verify_setup(&self.config).await,
|
||||
"list_symbols" => system::handle_list_symbols(&self.config).await,
|
||||
"healthcheck" => system::handle_healthcheck(&self.config, args).await,
|
||||
"verify_setup" => system::handle_verify_setup(&self.config).await,
|
||||
"list_symbols" => system::handle_list_symbols(&self.config).await,
|
||||
"healthcheck" => system::handle_healthcheck(&self.config, args).await,
|
||||
"get_active_account" => system::handle_get_active_account(&self.config).await,
|
||||
"check_update" => system::handle_check_update(&self.config).await,
|
||||
"update" => system::handle_update(&self.config).await,
|
||||
|
||||
// Expert/Indicator/Script handlers
|
||||
"list_experts" => experts::handle_list_experts(&self.config, args).await,
|
||||
|
||||
@@ -3,178 +3,6 @@ use serde_json::{json, Value};
|
||||
use std::path::Path;
|
||||
use crate::models::Config;
|
||||
|
||||
// ── Update helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
fn platform_tag() -> &'static str {
|
||||
#[cfg(all(target_os = "macos", target_arch = "aarch64"))] return "macos-aarch64";
|
||||
#[cfg(all(target_os = "macos", target_arch = "x86_64"))] return "macos-x86_64";
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))] return "linux-x86_64";
|
||||
#[cfg(not(any(
|
||||
all(target_os = "macos", target_arch = "aarch64"),
|
||||
all(target_os = "macos", target_arch = "x86_64"),
|
||||
all(target_os = "linux", target_arch = "x86_64"),
|
||||
)))] return "unsupported";
|
||||
}
|
||||
|
||||
fn semver_newer(latest: &str, current: &str) -> bool {
|
||||
let parse = |s: &str| -> (u32, u32, u32) {
|
||||
let mut p = s.trim_start_matches('v').splitn(3, '.');
|
||||
let ma = p.next().and_then(|x| x.parse().ok()).unwrap_or(0);
|
||||
let mi = p.next().and_then(|x| x.parse().ok()).unwrap_or(0);
|
||||
let pa = p.next().and_then(|x| x.parse().ok()).unwrap_or(0);
|
||||
(ma, mi, pa)
|
||||
};
|
||||
parse(latest) > parse(current)
|
||||
}
|
||||
|
||||
/// Fetch the latest release tag from GitHub API (5 s timeout via curl).
|
||||
/// Returns the version string without the leading "v", or None on failure.
|
||||
pub(super) async fn fetch_latest_version() -> Option<String> {
|
||||
let output = tokio::process::Command::new("curl")
|
||||
.args([
|
||||
"-sf", "--max-time", "5",
|
||||
"-H", "Accept: application/vnd.github.v3+json",
|
||||
"-H", "User-Agent: mt5-quant-updater",
|
||||
"https://api.github.com/repos/masdevid/mt5-quant/releases/latest",
|
||||
])
|
||||
.output()
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
if !output.status.success() { return None; }
|
||||
|
||||
let body: Value = serde_json::from_slice(&output.stdout).ok()?;
|
||||
body.get("tag_name")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.trim_start_matches('v').to_string())
|
||||
}
|
||||
|
||||
fn ok_response(data: Value) -> Value {
|
||||
json!({ "content": [{ "type": "text", "text": data.to_string() }], "isError": false })
|
||||
}
|
||||
|
||||
fn err_response(msg: impl std::fmt::Display) -> Value {
|
||||
json!({ "content": [{ "type": "text", "text": msg.to_string() }], "isError": true })
|
||||
}
|
||||
|
||||
// ── Update tool handlers ──────────────────────────────────────────────────────
|
||||
|
||||
pub async fn handle_check_update(_config: &Config) -> Result<Value> {
|
||||
let current = env!("CARGO_PKG_VERSION");
|
||||
|
||||
// Use cached background-check result if available; otherwise fetch now.
|
||||
let latest_opt = match super::LATEST_VERSION.get() {
|
||||
Some(v) => v.clone(),
|
||||
None => fetch_latest_version().await,
|
||||
};
|
||||
|
||||
let Some(latest) = latest_opt else {
|
||||
return Ok(ok_response(json!({
|
||||
"current_version": current,
|
||||
"update_available": false,
|
||||
"error": "Could not reach GitHub API — check network connectivity",
|
||||
})));
|
||||
};
|
||||
|
||||
let update_available = semver_newer(&latest, current);
|
||||
Ok(ok_response(json!({
|
||||
"current_version": current,
|
||||
"latest_version": latest,
|
||||
"update_available": update_available,
|
||||
"hint": if update_available {
|
||||
format!("Run the `update` tool to install v{latest}")
|
||||
} else {
|
||||
"You are on the latest version".to_string()
|
||||
},
|
||||
})))
|
||||
}
|
||||
|
||||
pub async fn handle_update(_config: &Config) -> Result<Value> {
|
||||
let current = env!("CARGO_PKG_VERSION");
|
||||
|
||||
let latest = match super::LATEST_VERSION.get().and_then(|v| v.as_deref()) {
|
||||
Some(v) => v.to_string(),
|
||||
None => match fetch_latest_version().await {
|
||||
Some(v) => v,
|
||||
None => return Ok(err_response(
|
||||
r#"{"success":false,"error":"Could not determine latest version — check network"}"#
|
||||
)),
|
||||
},
|
||||
};
|
||||
|
||||
if !semver_newer(&latest, current) {
|
||||
return Ok(ok_response(json!({
|
||||
"up_to_date": true,
|
||||
"version": current,
|
||||
})));
|
||||
}
|
||||
|
||||
let tag = platform_tag();
|
||||
if tag == "unsupported" {
|
||||
return Ok(err_response(
|
||||
r#"{"success":false,"error":"Auto-update not supported on this platform — build from source"}"#
|
||||
));
|
||||
}
|
||||
|
||||
let url = format!(
|
||||
"https://github.com/masdevid/mt5-quant/releases/download/v{latest}/mcp-mt5-quant-{tag}.tar.gz"
|
||||
);
|
||||
|
||||
// Download tarball to a temp file
|
||||
let tmp_tar = tempfile::NamedTempFile::new()?;
|
||||
let dl = tokio::process::Command::new("curl")
|
||||
.args(["-sfL", "--max-time", "120",
|
||||
"-o", tmp_tar.path().to_str().unwrap_or_default(),
|
||||
&url])
|
||||
.status()
|
||||
.await?;
|
||||
|
||||
if !dl.success() {
|
||||
return Ok(err_response(format!(
|
||||
r#"{{"success":false,"error":"Download failed","url":"{}"}}"#, url
|
||||
)));
|
||||
}
|
||||
|
||||
// Extract binary (tarball root dir is mcp-mt5-quant-{platform}/)
|
||||
let tmp_dir = tempfile::tempdir()?;
|
||||
let extract = tokio::process::Command::new("tar")
|
||||
.args(["-xzf", tmp_tar.path().to_str().unwrap_or_default(),
|
||||
"-C", tmp_dir.path().to_str().unwrap_or_default(),
|
||||
"--strip-components=1"])
|
||||
.status()
|
||||
.await?;
|
||||
|
||||
if !extract.success() {
|
||||
return Ok(err_response(r#"{"success":false,"error":"Failed to extract archive"}"#));
|
||||
}
|
||||
|
||||
let new_bin = tmp_dir.path().join("mt5-quant");
|
||||
if !new_bin.exists() {
|
||||
return Ok(err_response(r#"{"success":false,"error":"Binary not found in archive"}"#));
|
||||
}
|
||||
|
||||
// Atomic replace: write to sibling .tmp, then rename (safe on same FS)
|
||||
let current_exe = std::env::current_exe()?;
|
||||
let tmp_dest = current_exe.with_extension("update_tmp");
|
||||
std::fs::copy(&new_bin, &tmp_dest)?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::set_permissions(&tmp_dest, std::fs::Permissions::from_mode(0o755))?;
|
||||
}
|
||||
|
||||
std::fs::rename(&tmp_dest, ¤t_exe)?;
|
||||
|
||||
Ok(ok_response(json!({
|
||||
"success": true,
|
||||
"previous_version": current,
|
||||
"updated_to": latest,
|
||||
"binary": current_exe.to_string_lossy(),
|
||||
"hint": format!("Updated to v{latest}. Restart the MCP connection to load the new binary."),
|
||||
})))
|
||||
}
|
||||
|
||||
pub async fn handle_verify_setup(config: &Config) -> Result<Value> {
|
||||
let mut checks = serde_json::Map::new();
|
||||
let mut all_ok = true;
|
||||
|
||||
Reference in New Issue
Block a user