Compare commits
47
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72be381021 | ||
|
|
2a6cd8015a | ||
|
|
4216d704cd | ||
|
|
9ddabff95a | ||
|
|
7300124445 | ||
|
|
62987f035c | ||
|
|
5420713bbb | ||
|
|
5eab5feccb | ||
|
|
fdbaeb68a5 | ||
|
|
be41b43703 | ||
|
|
186b846936 | ||
|
|
b9ad93f873 | ||
|
|
16f10805f3 | ||
|
|
2ee8a72fe0 | ||
|
|
868752d39f | ||
|
|
cc464e85ad | ||
|
|
f87735538b | ||
|
|
af7715621d | ||
|
|
6647b189ec | ||
|
|
7eace36bb6 | ||
|
|
302a0c5624 | ||
|
|
608b07cead | ||
|
|
6db5460b99 | ||
|
|
fcf7ac26dc | ||
|
|
09b4fbd831 | ||
|
|
3cbb5b00a7 | ||
|
|
65b0346628 | ||
|
|
c65c4b5d1c | ||
|
|
b843146554 | ||
|
|
d8b0b37919 | ||
|
|
38d897d52f | ||
|
|
e3b04f51b6 | ||
|
|
66f728d545 | ||
|
|
06a4f8b87d | ||
|
|
ac50ae7bae | ||
|
|
dece88c514 | ||
|
|
2951f372e3 | ||
|
|
3c6da1d1d2 | ||
|
|
64cdc30df7 | ||
|
|
5c4bb2a863 | ||
|
|
5c2b4c52a0 | ||
|
|
e2cdd0684b | ||
|
|
8a56394f1c | ||
|
|
845fce7d68 | ||
|
|
019ddf7c21 | ||
|
|
599dcb14ba | ||
|
|
89d7ff5186 |
@@ -0,0 +1,35 @@
|
||||
name: CodeQL
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
schedule:
|
||||
- cron: '22 4 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (Rust)
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4
|
||||
with:
|
||||
languages: rust
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4
|
||||
@@ -4,6 +4,7 @@ on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
|
||||
+141
-73
@@ -23,15 +23,11 @@ jobs:
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write # Required for build provenance attestation (OIDC)
|
||||
attestations: write # Required to publish the attestation
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # v1
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
@@ -64,22 +60,13 @@ jobs:
|
||||
name: macos-binary
|
||||
path: dist/mcp-mt5-quant-macos-arm64.tar.gz
|
||||
|
||||
- name: Generate build provenance attestation
|
||||
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
||||
with:
|
||||
subject-path: dist/mcp-mt5-quant-macos-arm64.tar.gz
|
||||
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write # Required for build provenance attestation (OIDC)
|
||||
attestations: write # Required to publish the attestation
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # v1
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
@@ -112,11 +99,6 @@ jobs:
|
||||
name: linux-binary
|
||||
path: dist/mcp-mt5-quant-linux-x64.tar.gz
|
||||
|
||||
- name: Generate build provenance attestation
|
||||
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
||||
with:
|
||||
subject-path: dist/mcp-mt5-quant-linux-x64.tar.gz
|
||||
|
||||
# ── Cargo Publish ────────────────────────────────────────────────────────────
|
||||
|
||||
cargo-publish:
|
||||
@@ -128,7 +110,7 @@ jobs:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # v1
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
@@ -161,6 +143,11 @@ jobs:
|
||||
contents: write
|
||||
env:
|
||||
RELEASE_TAG: ${{ github.event.inputs.version || github.ref_name }}
|
||||
# secrets context is NOT available in step-level if: expressions —
|
||||
# referencing it there makes the whole workflow fail validation
|
||||
# (startup-failure runs on every push). Route the check through a
|
||||
# job-level env var instead: env IS readable in if:.
|
||||
HAS_GPG_SECRETS: ${{ secrets.GPG_PRIVATE_KEY != '' && secrets.GPG_PASSPHRASE != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
@@ -176,14 +163,49 @@ jobs:
|
||||
name: linux-binary
|
||||
path: dist
|
||||
|
||||
# ── GPG signing of release artifacts (delivery_unsigned) ────────────────
|
||||
# Guarded: only runs when BOTH signing secrets are configured. When they
|
||||
# are absent the release still ships (unsigned) instead of failing — so
|
||||
# forks/contributors without the secrets are not blocked. When present,
|
||||
# each binary gets a detached ASCII-armored signature and the public key
|
||||
# (docs/signing-key.asc) is attached to the release for verification.
|
||||
- name: Import GPG key and sign artifacts
|
||||
if: env.HAS_GPG_SECRETS == 'true'
|
||||
env:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
GPG_KEYID: EF1CA6A2E2D0CEFF61DC7F080256A0C36A6D5E72
|
||||
run: |
|
||||
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
|
||||
for f in dist/mcp-mt5-quant-macos-arm64.tar.gz \
|
||||
dist/mcp-mt5-quant-linux-x64.tar.gz; do
|
||||
gpg --batch --yes --pinentry-mode loopback \
|
||||
--passphrase "$GPG_PASSPHRASE" \
|
||||
--detach-sign --armor -u "$GPG_KEYID" "$f"
|
||||
done
|
||||
|
||||
- name: Build release asset list
|
||||
id: assets
|
||||
run: |
|
||||
{
|
||||
echo "files<<EOF"
|
||||
echo "dist/mcp-mt5-quant-macos-arm64.tar.gz"
|
||||
echo "dist/mcp-mt5-quant-linux-x64.tar.gz"
|
||||
echo "docs/signing-key.asc"
|
||||
if [ -f dist/mcp-mt5-quant-macos-arm64.tar.gz.asc ] && \
|
||||
[ -f dist/mcp-mt5-quant-linux-x64.tar.gz.asc ]; then
|
||||
echo "dist/mcp-mt5-quant-macos-arm64.tar.gz.asc"
|
||||
echo "dist/mcp-mt5-quant-linux-x64.tar.gz.asc"
|
||||
fi
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
|
||||
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
|
||||
with:
|
||||
tag_name: ${{ env.RELEASE_TAG }}
|
||||
name: ${{ env.RELEASE_TAG }}
|
||||
files: |
|
||||
dist/mcp-mt5-quant-macos-arm64.tar.gz
|
||||
dist/mcp-mt5-quant-linux-x64.tar.gz
|
||||
files: ${{ steps.assets.outputs.files }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
generate_release_notes: true
|
||||
@@ -192,13 +214,17 @@ jobs:
|
||||
|
||||
# ── MCP Package ──────────────────────────────────────────────────────────────
|
||||
# Builds the installable MCP package (binary + config + docs),
|
||||
# computes its SHA256, and uploads to the release.
|
||||
# computes its SHA256, and uploads to the release. Also attests the FINAL
|
||||
# release bytes — the macOS tarball is re-packaged here (server.json added),
|
||||
# so attesting in the build jobs would cover digests that never ship.
|
||||
|
||||
build-mcp-package:
|
||||
needs: release
|
||||
runs-on: macos-latest
|
||||
permissions:
|
||||
contents: write
|
||||
contents: write # Attach the package to the GitHub Release
|
||||
id-token: write # Required for build provenance attestation (OIDC)
|
||||
attestations: write # Required to publish the attestation
|
||||
outputs:
|
||||
sha256: ${{ steps.compute-sha256.outputs.sha256 }}
|
||||
env:
|
||||
@@ -212,6 +238,12 @@ jobs:
|
||||
name: macos-binary
|
||||
path: dist
|
||||
|
||||
- name: Download Linux binary
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: linux-binary
|
||||
path: dist
|
||||
|
||||
- name: Build MCP package
|
||||
run: |
|
||||
mkdir -p mcp-package/mcp-server/bin
|
||||
@@ -223,6 +255,19 @@ jobs:
|
||||
cp server.json mcp-package/
|
||||
cd mcp-package && tar -czf ../mcp-mt5-quant-macos-arm64.tar.gz . && cd ..
|
||||
|
||||
# Attest the exact bytes attached to the Release: the re-packaged macOS
|
||||
# tarball at the repo root and the pass-through Linux tarball from the
|
||||
# artifact — so attested digests match the published assets.
|
||||
- name: Generate build provenance attestation (macOS)
|
||||
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
||||
with:
|
||||
subject-path: mcp-mt5-quant-macos-arm64.tar.gz
|
||||
|
||||
- name: Generate build provenance attestation (Linux)
|
||||
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
||||
with:
|
||||
subject-path: dist/mcp-mt5-quant-linux-x64.tar.gz
|
||||
|
||||
- name: Compute SHA256
|
||||
id: compute-sha256
|
||||
run: |
|
||||
@@ -231,7 +276,7 @@ jobs:
|
||||
echo "MCP Package SHA256: $SHA256"
|
||||
|
||||
- name: Upload MCP package to release
|
||||
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
|
||||
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
|
||||
with:
|
||||
tag_name: ${{ env.RELEASE_TAG }}
|
||||
files: mcp-mt5-quant-macos-arm64.tar.gz
|
||||
@@ -250,6 +295,16 @@ jobs:
|
||||
# checks, which github-actions[bot] cannot bypass on a direct push. The PR
|
||||
# runs those checks legitimately, then auto-merges once green. This job
|
||||
# blocks until the merge lands so mcp-publish reads the patched server.json.
|
||||
#
|
||||
# Bot identity (RELEASE_PAT): when the RELEASE_PAT secret exists (a
|
||||
# fine-grained personal access token owned by masdevid with Contents+PR
|
||||
# write), the branch push and the PR are attributed to masdevid instead of
|
||||
# github-actions[bot]. GitHub only holds pull_request workflows for manual
|
||||
# approval when the PR author is an OUTSIDE contributor — same-owner PRs
|
||||
# run their checks immediately, making releases fully hands-off. Without
|
||||
# the secret everything falls back to GITHUB_TOKEN: the release still
|
||||
# completes, but the server.json PR's required status checks sit in
|
||||
# "action_required" until manually approved once per release.
|
||||
|
||||
update-server-json:
|
||||
needs: build-mcp-package
|
||||
@@ -257,6 +312,14 @@ jobs:
|
||||
permissions:
|
||||
contents: write # push the ci/* release branch
|
||||
pull-requests: write # create the PR and enable auto-merge
|
||||
env:
|
||||
# Single bot identity used for BOTH the git push and the gh pr calls —
|
||||
# GitHub ties workflow-trigger approval to the PR-authoring identity,
|
||||
# so push and PR must match. Resolves to RELEASE_PAT when configured,
|
||||
# otherwise the default GITHUB_TOKEN, so nothing breaks before the
|
||||
# secret is added. Declared here instead of interpolated inline so the
|
||||
# token never lands on a command line; GitHub masks it in logs anyway.
|
||||
RELEASE_PAT_OR_TOKEN: ${{ secrets.RELEASE_PAT || github.token }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
@@ -302,6 +365,13 @@ jobs:
|
||||
id: commit
|
||||
run: |
|
||||
BRANCH="ci/server-json-sha-${VERSION}"
|
||||
# Authenticate ALL pushes (stale-branch cleanup + branch upload)
|
||||
# with the SAME identity that creates the PR below: GitHub ties
|
||||
# workflow-trigger approval to the PR author, so a mismatch here
|
||||
# would defeat the RELEASE_PAT hands-off path. Token comes from the
|
||||
# job-level env (never interpolated inline, never echoed).
|
||||
git remote set-url --push origin \
|
||||
"https://x-access-token:${RELEASE_PAT_OR_TOKEN}@github.com/masdevid/mt5-quant.git"
|
||||
# Drop any stale branch/PR left over from a previous attempt at
|
||||
# this release so every run starts from a clean slate
|
||||
git push origin --delete "$BRANCH" 2>/dev/null || true
|
||||
@@ -334,20 +404,20 @@ jobs:
|
||||
# Same rule as above: no CI-skip marker in the title — GitHub
|
||||
# honors markers on PR titles too, which would suppress the
|
||||
# required checks.
|
||||
GH_TOKEN="${{ github.token }}" gh pr create \
|
||||
GH_TOKEN="$RELEASE_PAT_OR_TOKEN" gh pr create \
|
||||
--title "ci: update server.json SHA256 for v${VERSION}" \
|
||||
--body "Automated MCP package hash update." \
|
||||
--base master \
|
||||
--head "$BRANCH"
|
||||
|
||||
GH_TOKEN="${{ github.token }}" gh pr merge "$BRANCH" --auto --squash
|
||||
GH_TOKEN="$RELEASE_PAT_OR_TOKEN" gh pr merge "$BRANCH" --auto --squash
|
||||
|
||||
# Block until required checks (build, lint, cargo-deny x4) finish
|
||||
# and auto-merge completes — mcp-publish depends on this job and
|
||||
# checks out master expecting the patched server.json
|
||||
DEADLINE=$((SECONDS + 1800))
|
||||
while (( SECONDS < DEADLINE )); do
|
||||
STATE=$(GH_TOKEN="${{ github.token }}" gh pr view "$BRANCH" --json state --jq .state)
|
||||
STATE=$(GH_TOKEN="$RELEASE_PAT_OR_TOKEN" gh pr view "$BRANCH" --json state --jq .state)
|
||||
case "$STATE" in
|
||||
MERGED)
|
||||
echo "server.json PR merged to master"
|
||||
@@ -384,53 +454,51 @@ jobs:
|
||||
|
||||
- name: Install mcp-publisher
|
||||
id: install-publisher
|
||||
env:
|
||||
# Hash-pinned download (OpenSSF Scorecard Pinned-Dependencies):
|
||||
# PUB_SHA256 is the mcp-publisher_linux_amd64.tar.gz entry from
|
||||
# registry_1.8.1_checksums.txt shipped in the same release.
|
||||
# Bump both values together when upgrading.
|
||||
PUB_VERSION: v1.8.1
|
||||
PUB_SHA256: a06c9096dcb9727c13555b6be26c7effa707b01f06a4c561ba7a3635443cf2cc
|
||||
run: |
|
||||
# Fetch the download URL for linux_amd64 directly from the release assets API
|
||||
# (asset name format changed in v1.6.0 — look it up rather than constructing it)
|
||||
URL=$(curl -sf \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"https://api.github.com/repos/modelcontextprotocol/registry/releases/latest" \
|
||||
| python3 -c "
|
||||
import sys, json
|
||||
r = json.load(sys.stdin)
|
||||
tag = r['tag_name']
|
||||
assets = r.get('assets', [])
|
||||
match = next((a['browser_download_url'] for a in assets
|
||||
if 'linux' in a['name'] and 'amd64' in a['name']
|
||||
and a['name'].endswith('.tar.gz')
|
||||
and 'sbom' not in a['name']
|
||||
and 'sigstore' not in a['name']), None)
|
||||
if match:
|
||||
print(match)
|
||||
else:
|
||||
print(f'https://github.com/modelcontextprotocol/registry/releases/download/{tag}/mcp-publisher_linux_amd64.tar.gz', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
" 2>/dev/null)
|
||||
URL="https://github.com/modelcontextprotocol/registry/releases/download/${PUB_VERSION}/mcp-publisher_linux_amd64.tar.gz"
|
||||
|
||||
if [[ -z "$URL" ]]; then
|
||||
echo "Downloading: $URL"
|
||||
if ! curl -fsSL -o "$RUNNER_TEMP/mcp-publisher.tar.gz" "$URL"; then
|
||||
echo "installed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Could not resolve mcp-publisher download URL"
|
||||
else
|
||||
echo "Downloading: $URL"
|
||||
if curl -fsSL -o mcp-publisher.tar.gz "$URL"; then
|
||||
tar -xzf mcp-publisher.tar.gz 2>/dev/null || true
|
||||
BINARY=$(find . -maxdepth 3 -name "mcp-publisher" ! -path "./.git/*" | head -1)
|
||||
if [[ -n "$BINARY" && -f "$BINARY" ]]; then
|
||||
chmod +x "$BINARY"
|
||||
sudo mv "$BINARY" /usr/local/bin/mcp-publisher
|
||||
echo "installed=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Installed: $(mcp-publisher --version 2>/dev/null || echo 'ok')"
|
||||
else
|
||||
echo "installed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Binary not found in archive"
|
||||
fi
|
||||
rm -f mcp-publisher.tar.gz
|
||||
else
|
||||
echo "installed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Download failed: $URL"
|
||||
fi
|
||||
echo "Download failed: $URL"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Never execute unverified bytes: hard-fail the job on any digest
|
||||
# mismatch (upstream re-release or tampering)
|
||||
ACTUAL=$(sha256sum "$RUNNER_TEMP/mcp-publisher.tar.gz" | awk '{print $1}')
|
||||
if [[ "$ACTUAL" != "$PUB_SHA256" ]]; then
|
||||
echo "::error::SHA256 mismatch for mcp-publisher ${PUB_VERSION}: expected ${PUB_SHA256}, got ${ACTUAL}"
|
||||
exit 1
|
||||
fi
|
||||
echo "SHA256 verified: $ACTUAL"
|
||||
|
||||
EXTRACT_DIR="$(mktemp -d)"
|
||||
if ! tar -xzf "$RUNNER_TEMP/mcp-publisher.tar.gz" -C "$EXTRACT_DIR"; then
|
||||
rm -rf "$EXTRACT_DIR" "$RUNNER_TEMP/mcp-publisher.tar.gz"
|
||||
echo "installed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Extraction failed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -f "$EXTRACT_DIR/mcp-publisher" ]]; then
|
||||
chmod +x "$EXTRACT_DIR/mcp-publisher"
|
||||
sudo mv "$EXTRACT_DIR/mcp-publisher" /usr/local/bin/mcp-publisher
|
||||
echo "installed=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Installed: $(mcp-publisher --version 2>/dev/null || echo 'ok')"
|
||||
else
|
||||
echo "installed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Binary not found in archive"
|
||||
fi
|
||||
rm -rf "$EXTRACT_DIR" "$RUNNER_TEMP/mcp-publisher.tar.gz"
|
||||
|
||||
- name: Validate server.json before publish
|
||||
id: validate
|
||||
run: |
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # v1
|
||||
- uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # v1
|
||||
with:
|
||||
toolchain: stable
|
||||
components: clippy, rustfmt
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
name: Scorecard supply-chain security
|
||||
on:
|
||||
branch_protection_rule:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '30 6 * * 3'
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
permissions: read-all
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
@@ -15,7 +17,7 @@ jobs:
|
||||
permissions:
|
||||
security-events: write
|
||||
id-token: write
|
||||
contents: read
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -33,6 +35,47 @@ jobs:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
|
||||
- uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4
|
||||
- uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
- name: Update badge JSON
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
import urllib.request
|
||||
|
||||
url = "https://api.scorecard.dev/projects/github.com/masdevid/mt5-quant"
|
||||
with urllib.request.urlopen(url) as resp:
|
||||
data = json.load(resp)
|
||||
|
||||
score = float(data["score"])
|
||||
if score >= 9:
|
||||
color = "brightgreen"
|
||||
elif score >= 7:
|
||||
color = "yellowgreen"
|
||||
elif score >= 5:
|
||||
color = "yellow"
|
||||
else:
|
||||
color = "red"
|
||||
|
||||
badge = {
|
||||
"schemaVersion": 1,
|
||||
"label": "OpenSSF Scorecard",
|
||||
"message": f"{score:g}",
|
||||
"color": color,
|
||||
}
|
||||
with open("badge.json", "w") as f:
|
||||
json.dump(badge, f)
|
||||
print("Wrote badge.json:", badge)
|
||||
PY
|
||||
|
||||
git remote set-url origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git"
|
||||
git checkout -B badges
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add -f badge.json
|
||||
git commit -m "chore: update OpenSSF Scorecard badge" || echo "no changes to commit"
|
||||
git push -f origin badges
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Contributing to MT5-Quant
|
||||
|
||||
Thanks for your interest in improving MT5-Quant! This document explains how to
|
||||
contribute and what we expect from contributions.
|
||||
|
||||
## How to contribute
|
||||
|
||||
1. **Fork** the repository and create a topic branch from `master`.
|
||||
2. Make your change, keeping it focused (one logical change per PR).
|
||||
3. Open a **pull request against `master`**. A pull request template
|
||||
(`.github/PULL_REQUEST_TEMPLATE.md`) is provided — please fill it in.
|
||||
4. Ensure CI passes (build, `cargo test`, clippy, formatting, cargo-deny,
|
||||
CodeQL). Maintainers review and merge once checks and review are satisfied.
|
||||
|
||||
## Development setup
|
||||
|
||||
- Install Rust (stable toolchain) and, for full backtests, Wine/MT5 as described
|
||||
in `README.md` and `docs/QUICKSTART.md`.
|
||||
- Build the release binary with either:
|
||||
- `bash scripts/build-rust.sh`, or
|
||||
- `cargo build --release`
|
||||
- The binary is produced at `target/release/mt5-quant`.
|
||||
|
||||
## Testing requirement
|
||||
|
||||
- **Non-trivial changes must include tests.** New functionality should come with
|
||||
unit or integration tests where practical.
|
||||
- `cargo test` runs automatically in CI (`rust.yml`) on every push and pull
|
||||
request, so a change that breaks tests cannot merge.
|
||||
|
||||
## Commit style
|
||||
|
||||
- Follow **Conventional Commits** (`feat:`, `fix:`, `docs:`, `refactor:`,
|
||||
`test:`, `release:`), as documented in `AGENTS.md`.
|
||||
|
||||
## Issue tracker
|
||||
|
||||
- Bugs and enhancement requests are tracked on the project's
|
||||
[GitHub Issues](https://github.com/masdevid/mt5-quant/issues), with
|
||||
bug-report and feature-request templates under `.github/ISSUE_TEMPLATE/`.
|
||||
Generated
+42
-19
@@ -138,9 +138,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.6"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
|
||||
checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -148,9 +148,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.6"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
|
||||
checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -160,9 +160,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.4"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
|
||||
checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -189,10 +189,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
name = "core_detect"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||
checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "7.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d57d423b3c82e89b9a24ca3091fee61f456a26edbd28d26c65906f4bc1dcd8f"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
@@ -211,11 +217,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
version = "0.8.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"core_detect",
|
||||
"multiversion_no_op",
|
||||
"rustversion",
|
||||
"scopeguard",
|
||||
"simdutf8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -500,6 +511,12 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multiversion_no_op"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d"
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
@@ -656,9 +673,9 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb"
|
||||
checksum = "9da7f59328b09405b181fee1018925fad50f157afb43c0c0f5c522c256dd5907"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -766,9 +783,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.151"
|
||||
version = "1.0.154"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -815,6 +832,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simdutf8"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
@@ -915,9 +938,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.53.1"
|
||||
version = "1.54.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
|
||||
checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
@@ -1024,9 +1047,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.24.1"
|
||||
version = "1.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9"
|
||||
checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514"
|
||||
dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"js-sys",
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
uuid = { version = "1.0", features = ["v4"] }
|
||||
regex = "1.0"
|
||||
dirs = "6.0"
|
||||
dirs = "7.0"
|
||||
walkdir = "2.0"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
encoding_rs = "0.8"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MT5-Quant
|
||||
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/masdevid/mt5-quant) [](https://github.com/masdevid/mt5-quant/actions/workflows/rust.yml) [](https://crates.io/crates/mt5-quant) [](LICENSE)
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/masdevid/mt5-quant) [](https://bestpractices.coreinfrastructure.org/projects/14227) [](https://github.com/masdevid/mt5-quant/actions/workflows/rust.yml) [](https://crates.io/crates/mt5-quant) [](LICENSE) [](https://github.com/masdevid/mt5-quant/security/code-scanning)
|
||||
|
||||
**MCP server for MT5 strategy development on macOS/Linux.** 89 tools to compile, backtest, analyze, optimize, debug crashes, and manage MQL5 Expert Advisors — no Windows required.
|
||||
|
||||
@@ -33,14 +33,68 @@ Claude: [compile → clean → backtest → analyze 1,847 deals]
|
||||
|
||||
### Install MT5-Quant
|
||||
|
||||
Ask your LLM coding platform to install and configure MT5-Quant:
|
||||
#### Option A — Cargo (recommended, needs Rust toolchain)
|
||||
|
||||
> "Please install mt5-quant. It's a Rust-based MCP server for MT5 backtesting at https://github.com/masdevid/mt5-quant. Run its `scripts/setup.sh` to auto-detect Wine and MT5 paths, register the MCP server, and configure everything."
|
||||
```bash
|
||||
cargo install mt5-quant --locked
|
||||
```
|
||||
|
||||
The LLM will:
|
||||
1. Download the pre-built binary (or `cargo build --release`)
|
||||
2. Run `scripts/setup.sh` to auto-detect Wine/MT5 and write config
|
||||
3. Register the MCP server with your coding platform
|
||||
#### Option B — Pre-built binaries (no toolchain)
|
||||
|
||||
```bash
|
||||
# macOS Apple Silicon
|
||||
curl -LO https://github.com/masdevid/mt5-quant/releases/latest/download/mcp-mt5-quant-macos-arm64.tar.gz
|
||||
tar xzf mcp-mt5-quant-macos-arm64.tar.gz # → mcp-mt5-quant-macos/mt5-quant
|
||||
|
||||
# Linux x64
|
||||
curl -LO https://github.com/masdevid/mt5-quant/releases/latest/download/mcp-mt5-quant-linux-x64.tar.gz
|
||||
tar xzf mcp-mt5-quant-linux-x64.tar.gz # → mcp-mt5-quant-linux/mt5-quant
|
||||
```
|
||||
|
||||
Move the binary somewhere on your `PATH` (e.g. `/usr/local/bin` or `~/.local/bin`) so clients can launch it as `mt5-quant`.
|
||||
|
||||
#### Option C — Register with your LLM client (copy-paste)
|
||||
|
||||
**Claude Code**
|
||||
|
||||
```bash
|
||||
claude mcp add mt5-quant -- mt5-quant --stdio
|
||||
```
|
||||
|
||||
**Claude Desktop** — `~/Library/Application Support/Claude/claude_desktop_config.json`
|
||||
|
||||
```json
|
||||
{ "mcpServers": { "mt5-quant": { "command": "mt5-quant", "args": ["--stdio"] } } }
|
||||
```
|
||||
|
||||
**Cursor** — `.cursor/mcp.json` (same JSON shape as Claude Desktop)
|
||||
|
||||
```json
|
||||
{ "mcpServers": { "mt5-quant": { "command": "mt5-quant", "args": ["--stdio"] } } }
|
||||
```
|
||||
|
||||
**opencode** — `.opencode/opencode.jsonc`
|
||||
|
||||
```jsonc
|
||||
{ "mcp": { "mt5-quant": { "type": "local", "command": ["mt5-quant", "--stdio"], "enabled": true } } }
|
||||
```
|
||||
|
||||
**Codex CLI** — `~/.codex/config.toml`
|
||||
|
||||
```toml
|
||||
[mcp_servers.mt5-quant]
|
||||
command = "mt5-quant"
|
||||
args = ["--stdio"]
|
||||
```
|
||||
|
||||
Then finish setup (auto-detects Wine + MT5 paths):
|
||||
|
||||
```bash
|
||||
bash scripts/setup.sh
|
||||
```
|
||||
|
||||
> **Tip:** you can also just ask your LLM coding agent to do all of the above:
|
||||
> *"Please install mt5-quant from https://github.com/masdevid/mt5-quant — download or build it, run its `scripts/setup.sh` to auto-detect Wine and MT5 paths, and register it as an MCP server."*
|
||||
|
||||
### First Backtest
|
||||
|
||||
@@ -228,6 +282,19 @@ For crashes or unexplained failures during backtest/compile/optimization:
|
||||
|
||||
**[Full Troubleshooting Guide →](docs/TROUBLESHOOTING.md)**
|
||||
|
||||
## Documentation
|
||||
|
||||
| Doc | Description |
|
||||
|-----|-------------|
|
||||
| [QUICKSTART](docs/QUICKSTART.md) | Step-by-step install, setup, and first backtest |
|
||||
| [MCP_TOOLS](docs/MCP_TOOLS.md) | Full input/output schemas for all 90 tools |
|
||||
| [CONFIG](docs/CONFIG.md) | Configuration reference (`config/mt5-quant.yaml`) |
|
||||
| [ARCHITECTURE](docs/ARCHITECTURE.md) | Design principles and internal module layout |
|
||||
| [REMOTE_AGENTS](docs/REMOTE_AGENTS.md) | Distribute optimization across Linux agents via Wine |
|
||||
| [TROUBLESHOOTING](docs/TROUBLESHOOTING.md) | Platform-specific fixes for Wine, MT5, and backtests |
|
||||
|
||||
Full index with reading order: [docs/INDEX.md](docs/INDEX.md)
|
||||
|
||||
---
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
+140
-21
@@ -1,35 +1,154 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
This document describes how to report security issues in **mt5-quant**, and the
|
||||
secure-development practices the project follows. It backs the OpenSSF Best
|
||||
Practices criteria `vulnerability_report_process`, `know_secure_design`, and
|
||||
`know_common_errors`, and the signed-release process described under
|
||||
[Signing](#signing-of-release-artifacts) backs `delivery_unsigned`.
|
||||
|
||||
Only the latest minor release line receives security fixes. Older versions should upgrade.
|
||||
mt5-quant is a Rust MCP server that drives MetaTrader 5 (under Wine/CrossOver)
|
||||
to compile, backtest, and analyze trading Expert Advisors. It runs **locally**;
|
||||
it never exposes a network listener and never transmits user data off the
|
||||
machine except for the opt-in version self-check (HTTPS to GitHub). The security
|
||||
notes below focus on the real risks in that model: untrusted input from MT5
|
||||
report files, the user-supplied Wine/MT5 configuration, and the build/publish
|
||||
pipeline.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 1.34.x | :white_check_mark: |
|
||||
| < 1.34 | :x: |
|
||||
## Vulnerability reporting process
|
||||
|
||||
## Reporting a Vulnerability
|
||||
We encourage responsible disclosure. If you discover a vulnerability, please
|
||||
report it through **one** of the following channels (in order of preference):
|
||||
|
||||
**Please do not open public GitHub issues for security matters.**
|
||||
1. **GitHub Security Advisories (recommended).** Open a private security
|
||||
advisory at <https://github.com/masdevid/mt5-quant/security/advisories/new>.
|
||||
This keeps the details confidential while we prepare a fix.
|
||||
2. **Public GitHub Issue.** If you prefer, open a normal issue at
|
||||
<https://github.com/masdevid/mt5-quant/issues>. Public reports are accepted;
|
||||
please avoid posting working exploits in the open issue body — link to a
|
||||
private gist or email the maintainer instead.
|
||||
|
||||
Preferred: use GitHub [Private Vulnerability Reporting](https://github.com/masdevid/mt5-quant/security/advisories/new) — **Security tab → Report a vulnerability**. This keeps the report confidential while we work on a fix.
|
||||
### What to include
|
||||
|
||||
Please include as much of the following as you can:
|
||||
A useful report contains:
|
||||
|
||||
- Affected version (and commit SHA, if relevant)
|
||||
- Steps to reproduce or a proof of concept
|
||||
- Expected vs. actual behavior
|
||||
- Any known mitigations
|
||||
- A clear description of the vulnerability and its impact.
|
||||
- The affected version(s) (`mt5-quant --version` or the `version` field in
|
||||
`Cargo.toml`).
|
||||
- Steps to reproduce, or a proof-of-concept input (e.g. a crafted MT5 report,
|
||||
`.set` file, or tool argument).
|
||||
- Any relevant logs, crashes, or sanitizer/fuzzer output.
|
||||
|
||||
## Response Commitment
|
||||
### Expected handling timeline
|
||||
|
||||
- **Acknowledgement:** within **72 hours** of your report
|
||||
- **Fix target:** vulnerabilities rated **critical/high** are targeted for a patch release within **14 days**
|
||||
- We will keep you informed of progress and credit you in the advisory unless you prefer otherwise
|
||||
- **Acknowledgement:** within 7 days of a complete report.
|
||||
- **Triage & severity assessment:** within 14 days.
|
||||
- **Fix or mitigation plan:** timeline depends on severity; critical issues are
|
||||
addressed as soon as a patch is ready, and a coordinated disclosure date is
|
||||
agreed with the reporter when a private advisory is used.
|
||||
- **Disclosure:** once a fixed release is published, the advisory (if used) is
|
||||
made public and the fix is credited to the reporter unless they request
|
||||
anonymity.
|
||||
|
||||
## Scope Notes
|
||||
There is no formal contractual SLA; reports are handled on a best-effort,
|
||||
case-by-case basis through the public channels above.
|
||||
|
||||
MT5-Quant drives Wine and MetaTrader 5 locally — it launches external processes with paths, arguments, and configuration sourced from its YAML config file, `.set` parameter files, and MCP tool arguments. Reports concerning path traversal, config injection, or unsafe command construction through these surfaces are very much in scope and welcome.
|
||||
## Secure development practices (`know_secure_design`)
|
||||
|
||||
Out of scope: vulnerabilities in Wine, MetaTrader 5 itself, or broker infrastructure — please report those upstream to the respective vendors.
|
||||
mt5-quant follows these practices to keep the code trustworthy:
|
||||
|
||||
- **Input validation at every tool boundary.** All 89 MCP tool handlers in
|
||||
`src/tools/handlers/` parse and validate their arguments at the entry point
|
||||
(using helpers such as `required_str`, `resolve_report`, and
|
||||
`prepare_analysis`). Untrusted data — tool arguments, the filesystem, and MT5
|
||||
output — is never trusted inside core logic.
|
||||
- **No hardcoded secrets.** The source tree contains no embedded credentials,
|
||||
API tokens, or private keys. The only secrets are user-provided: a local
|
||||
`config/mt5-quant.yaml` (gitignored) holding the Wine/MT5 paths, and an
|
||||
optional release PAT used only in CI. Neither is persisted by the project
|
||||
itself.
|
||||
- **Safe parsing of untrusted MT5 reports.** Backtest/optimization reports come
|
||||
from MetaTrader 5 as either HTML or SpreadsheetML XML (`.htm.xml`, Build 48+).
|
||||
Parsers in `src/analytics/extract.rs` and `src/optimization/parser.rs` use
|
||||
explicit, bounded parsing (no `eval`, no regex-backed HTML/script execution)
|
||||
and fail fast on malformed input. These parsers are additionally exercised by
|
||||
the cargo-fuzz harness in `fuzz/` (see `dynamic_analysis*` criteria).
|
||||
- **Least privilege at runtime.** The architecture enforces **a single MT5
|
||||
instance** and never runs two backtests in parallel on the same Wine prefix.
|
||||
When no display is available the pipeline falls back to headless/Xvfb mode; the
|
||||
only external process launched is the user-configured Wine/MT5 binary.
|
||||
- **Dependency auditing in CI.** Every push and pull request runs
|
||||
`cargo-deny` (license + advisory + source checks) and the GitHub
|
||||
`dependency-review` action. Vulnerable or non-compliant dependencies block the
|
||||
build via the `protect-master` ruleset's required status checks.
|
||||
- **Fuzzing of parsers.** `fuzz/` contains libFuzzer targets that feed arbitrary
|
||||
bytes into the report/metrics parsers with debug assertions and integer-
|
||||
overflow traps enabled, catching panics and invariant violations.
|
||||
- **Isolation of the Wine/MT5 sandbox.** MT5 runs under the user's Wine prefix,
|
||||
which is isolated from the host shell environment. The server does not grant
|
||||
MT5 any elevated privileges.
|
||||
- **Static analysis.** Clippy runs with `-D warnings` (warnings fail the build)
|
||||
and GitHub CodeQL (Rust) scans every push/PR.
|
||||
|
||||
## Common implementation errors we avoid (`know_common_errors`)
|
||||
|
||||
The codebase is deliberately written to avoid well-known Rust/systems pitfalls:
|
||||
|
||||
- **Integer overflow.** Rust's release builds disable overflow checks by
|
||||
default, so arithmetic on deal volumes, prices, and cumulative PnL uses
|
||||
`checked_*`/`saturating_*` operations or is validated at parse time. The
|
||||
fuzz harness re-enables `debug-assertions` so overflow traps fire during
|
||||
testing.
|
||||
- **Path traversal in `.set` file handling.** `.set` files (UTF-16LE parameter
|
||||
files for EAs) are read/written through helpers in
|
||||
`src/tools/handlers/setfiles.rs`. Paths are resolved against the configured
|
||||
tester profiles directory and normalized; user-supplied filenames are never
|
||||
interpolated into shell commands and are confined to that directory.
|
||||
- **UTF-16LE encoding pitfalls.** MetaTrader strips `||Y` flags from UTF-8 `.set`
|
||||
files, so all writes use UTF-16LE with an explicit BOM and `chmod 444`. The
|
||||
helpers never assume UTF-8, avoiding silent mojibake / flag loss.
|
||||
- **Avoiding `unsafe`.** The crate uses no `unsafe` blocks in application logic;
|
||||
all FFI with Wine/MT5 is performed via the `std::process` API and the system
|
||||
`curl`/`gpg` binaries, not raw pointer manipulation.
|
||||
- **Proper error propagation.** Errors are returned as typed `Result`s and
|
||||
propagated with `?`; there are no swallowed errors or empty `catch`-and-ignore
|
||||
paths. Invalid states halt fast with descriptive messages (Fail-Fast
|
||||
philosophy) rather than being silently patched.
|
||||
- **No injection via shell.** Wine/MT5 is launched with an argument vector (not
|
||||
a constructed shell string), eliminating command-injection from config values.
|
||||
|
||||
## Signing of release artifacts
|
||||
|
||||
To satisfy `delivery_unsigned`, **release artifacts are GPG-signed**:
|
||||
|
||||
- Each GitHub Release publishes the macOS (`mcp-mt5-quant-macos-arm64.tar.gz`)
|
||||
and Linux (`mcp-mt5-quant-linux-x64.tar.gz`) binaries **plus** their detached
|
||||
ASCII-armored signatures (`*.tar.gz.asc`), and the project's public key at
|
||||
**`docs/signing-key.asc`**.
|
||||
- The signing key is an RSA-4096 key generated for the `mt5-quant releases`
|
||||
identity. The public half is committed to the repository; the private half is
|
||||
held only in CI secrets (`GPG_PRIVATE_KEY`, `GPG_PASSPHRASE`) and is never
|
||||
committed.
|
||||
- Signing is performed in CI (`release.yml`) with
|
||||
`gpg --detach-sign --armor` and is **guarded** so the release still succeeds
|
||||
if the signing secrets are not configured — it simply omits the signatures in
|
||||
that case.
|
||||
|
||||
### Verifying a downloaded release
|
||||
|
||||
```bash
|
||||
# 1. Import the project public key
|
||||
gpg --import docs/signing-key.asc
|
||||
|
||||
# 2. Verify a release artifact
|
||||
gpg --verify mcp-mt5-quant-linux-x64.tar.gz.asc \
|
||||
mcp-mt5-quant-linux-x64.tar.gz
|
||||
|
||||
# A good signature from "mt5-quant releases <releases@mt5-quant.local>"
|
||||
# confirms the artifact was produced by the project's release pipeline.
|
||||
```
|
||||
|
||||
## Supported versions
|
||||
|
||||
Security fixes are applied to the latest released version on the `master`
|
||||
branch. Older releases are not back-ported; users should upgrade to the current
|
||||
release to receive fixes.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Documentation Index
|
||||
|
||||
**Start here:** [QUICKSTART.md](QUICKSTART.md) — install MT5-Quant, run `scripts/setup.sh` to auto-detect Wine/MT5 paths, register it with your LLM client, and complete your first backtest.
|
||||
|
||||
| Doc | Description |
|
||||
|-----|-------------|
|
||||
| [QUICKSTART.md](QUICKSTART.md) | Step-by-step install, setup, and first backtest |
|
||||
| [MCP_TOOLS.md](MCP_TOOLS.md) | Full input/output schemas for all 90 tools |
|
||||
| [CONFIG.md](CONFIG.md) | Configuration reference (`config/mt5-quant.yaml`) |
|
||||
| [ARCHITECTURE.md](ARCHITECTURE.md) | Design principles and internal module layout |
|
||||
| [REMOTE_AGENTS.md](REMOTE_AGENTS.md) | Distribute optimization across Linux agents via Wine |
|
||||
| [TROUBLESHOOTING.md](TROUBLESHOOTING.md) | Platform-specific fixes for Wine, MT5, and backtests |
|
||||
|
||||
## Suggested reading order
|
||||
|
||||
1. [QUICKSTART.md](QUICKSTART.md) — get a working installation and first report
|
||||
2. [MCP_TOOLS.md](MCP_TOOLS.md) — browse what your LLM agent can call
|
||||
3. [CONFIG.md](CONFIG.md) — tune symbols, timeframes, deposit defaults
|
||||
4. [TROUBLESHOOTING.md](TROUBLESHOOTING.md) — when something fails
|
||||
5. [ARCHITECTURE.md](ARCHITECTURE.md) — how the pipeline works under the hood
|
||||
6. [REMOTE_AGENTS.md](REMOTE_AGENTS.md) — scale up optimization throughput
|
||||
|
||||
These files are also bundled in every release tarball and MCP package, so installed copies stay available offline.
|
||||
@@ -0,0 +1,65 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
GqF/jfDJVQqPMNY=
|
||||
=sjIH
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@@ -0,0 +1,4 @@
|
||||
target
|
||||
corpus
|
||||
artifacts
|
||||
coverage
|
||||
Generated
+1204
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
[package]
|
||||
name = "mt5-quant-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
|
||||
[dependencies.mt5-quant]
|
||||
path = ".."
|
||||
|
||||
[[bin]]
|
||||
name = "parse_metrics"
|
||||
path = "fuzz_targets/parse_metrics.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
# Enable debug/overflow assertions during fuzzing. cargo-fuzz builds fuzz
|
||||
# targets in release mode by default, so we explicitly turn debug-assertions
|
||||
# back on for the release profile. This backs the
|
||||
# `dynamic_analysis_enable_assertions` CII criterion: any debug_assert! or
|
||||
# arithmetic-overflow check inside the crate is active while libFuzzer feeds
|
||||
# arbitrary input, so crashes from invariant violations are caught as bugs.
|
||||
[profile.release]
|
||||
debug-assertions = true
|
||||
|
||||
[profile.dev]
|
||||
debug-assertions = true
|
||||
@@ -0,0 +1,36 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use mt5_quant::models::Metrics;
|
||||
|
||||
// Fuzz target for the pure, I/O-free MT5 backtest report metrics parser.
|
||||
//
|
||||
// `Metrics::from_html` takes an arbitrary `&str` and extracts the numeric
|
||||
// summary metrics from an MT5 HTML/XML report. It is panic-free on bad input:
|
||||
// every numeric parse uses `unwrap_or`, regex patterns are compile-time
|
||||
// constants (never panicking), and the function returns `Option<Metrics>`
|
||||
// (None when no metrics are found). We return early on non-UTF-8 input and on
|
||||
// the None case, so the fuzzer only ever exercises the parser itself.
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let Ok(text) = std::str::from_utf8(data) else {
|
||||
// Parser only accepts UTF-8 text; arbitrary byte sequences are
|
||||
// out of scope and must not cause a crash.
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(metrics) = Metrics::from_html(text) {
|
||||
// Force the computed fields to be materialized so any logic error
|
||||
// inside the parser surfaces during fuzzing.
|
||||
let _ = (
|
||||
metrics.net_profit,
|
||||
metrics.profit_factor,
|
||||
metrics.max_dd_pct,
|
||||
metrics.sharpe_ratio,
|
||||
metrics.total_trades,
|
||||
metrics.recovery_factor,
|
||||
metrics.win_rate_pct,
|
||||
metrics.gross_profit,
|
||||
metrics.gross_loss,
|
||||
);
|
||||
}
|
||||
});
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
"registryType": "mcpb",
|
||||
"version": "1.34.3",
|
||||
"identifier": "https://github.com/masdevid/mt5-quant/releases/download/v1.34.3/mcp-mt5-quant-macos-arm64.tar.gz",
|
||||
"fileSha256": "TBD_CI_WILL_UPDATE",
|
||||
"fileSha256": "2f303cb3deb62e47742e5eb4d73a0e3edf200aadf7db9da39a0ef2151d064171",
|
||||
"transport": {
|
||||
"type": "stdio"
|
||||
},
|
||||
|
||||
@@ -7,6 +7,12 @@ use crate::models::metrics::Metrics;
|
||||
|
||||
pub struct DealAnalyzer;
|
||||
|
||||
impl Default for DealAnalyzer {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl DealAnalyzer {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
|
||||
@@ -8,6 +8,12 @@ use crate::models::{Deal, Metrics};
|
||||
|
||||
pub struct ReportExtractor;
|
||||
|
||||
impl Default for ReportExtractor {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ReportExtractor {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
pub mod analytics;
|
||||
pub mod compile;
|
||||
pub mod models;
|
||||
pub mod optimization;
|
||||
pub mod pipeline;
|
||||
pub mod storage;
|
||||
pub mod tools;
|
||||
|
||||
pub mod mcp_server;
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct McpRequest {
|
||||
pub jsonrpc: String,
|
||||
pub id: Option<serde_json::Value>,
|
||||
pub method: String,
|
||||
pub params: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct McpResponse {
|
||||
pub jsonrpc: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub result: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error: Option<McpError>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct McpError {
|
||||
pub code: i32,
|
||||
pub message: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct ServerInfo {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct InitializeResult {
|
||||
pub protocol_version: String,
|
||||
pub capabilities: ServerCapabilities,
|
||||
pub server_info: ServerInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct ServerCapabilities {
|
||||
pub experimental: serde_json::Value,
|
||||
pub tools: ToolCapabilities,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct ToolCapabilities {
|
||||
pub list_changed: bool,
|
||||
}
|
||||
+9
-68
@@ -1,24 +1,16 @@
|
||||
mod analytics;
|
||||
mod compile;
|
||||
mod models;
|
||||
mod optimization;
|
||||
mod pipeline;
|
||||
mod storage;
|
||||
mod tools;
|
||||
|
||||
mod mcp_server;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use serde_json::{json, Value};
|
||||
use serde_json::json;
|
||||
use std::io::{stdout, Write};
|
||||
use std::time::Instant;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::net::TcpListener;
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::models::Config;
|
||||
use crate::pipeline::backtest::{BacktestParams, BacktestPipeline};
|
||||
use mt5_quant::mcp_server::{McpServer, Notification};
|
||||
use mt5_quant::models::Config;
|
||||
use mt5_quant::pipeline::backtest::{BacktestParams, BacktestPipeline};
|
||||
use mt5_quant::{McpError, McpRequest, McpResponse};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "mt5-quant")]
|
||||
@@ -45,57 +37,6 @@ struct Cli {
|
||||
startup_delay: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct McpRequest {
|
||||
jsonrpc: String,
|
||||
id: Option<Value>,
|
||||
method: String,
|
||||
params: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct McpResponse {
|
||||
jsonrpc: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
id: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
result: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
error: Option<McpError>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct McpError {
|
||||
code: i32,
|
||||
message: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
data: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct ServerInfo {
|
||||
name: String,
|
||||
version: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct InitializeResult {
|
||||
protocol_version: String,
|
||||
capabilities: ServerCapabilities,
|
||||
server_info: ServerInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct ServerCapabilities {
|
||||
experimental: Value,
|
||||
tools: ToolCapabilities,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct ToolCapabilities {
|
||||
list_changed: bool,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
@@ -178,9 +119,9 @@ async fn run_test_launch(ea: Option<String>, startup_delay: Option<u64>) -> Resu
|
||||
async fn run_stdio_server() -> Result<()> {
|
||||
info!("Starting MT5-Quant MCP server on stdio");
|
||||
|
||||
let server = std::sync::Arc::new(mcp_server::McpServer::new());
|
||||
let server = std::sync::Arc::new(McpServer::new());
|
||||
let (notification_tx, mut notification_rx) =
|
||||
tokio::sync::mpsc::unbounded_channel::<mcp_server::Notification>();
|
||||
tokio::sync::mpsc::unbounded_channel::<Notification>();
|
||||
server.set_notification_sender(notification_tx).await;
|
||||
|
||||
// Spawn notification sender task
|
||||
@@ -254,7 +195,7 @@ async fn run_tcp_server(port: u16) -> Result<()> {
|
||||
info!("Starting MT5-Quant MCP server on TCP port {}", port);
|
||||
|
||||
let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).await?;
|
||||
info!("Listening on 127.0.0.1:{}", port);
|
||||
info!("Running TCP server on port {}", port);
|
||||
|
||||
loop {
|
||||
let (socket, addr) = listener.accept().await?;
|
||||
@@ -272,7 +213,7 @@ async fn handle_connection(socket: tokio::net::TcpStream) -> Result<()> {
|
||||
let (reader, mut writer) = socket.into_split();
|
||||
let mut reader = BufReader::new(reader);
|
||||
let mut line = String::new();
|
||||
let server = mcp_server::McpServer::new();
|
||||
let server = McpServer::new();
|
||||
|
||||
loop {
|
||||
line.clear();
|
||||
|
||||
@@ -31,6 +31,12 @@ pub struct McpServer {
|
||||
notification_tx: Arc<Mutex<Option<mpsc::UnboundedSender<Notification>>>>,
|
||||
}
|
||||
|
||||
impl Default for McpServer {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl McpServer {
|
||||
pub fn new() -> Self {
|
||||
let config = ModelsConfig::load().unwrap_or_default();
|
||||
|
||||
@@ -17,6 +17,12 @@ pub struct OptimizationPass {
|
||||
|
||||
pub struct OptimizationParser;
|
||||
|
||||
impl Default for OptimizationParser {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl OptimizationParser {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
|
||||
@@ -40,6 +40,13 @@ impl Stage {
|
||||
#[allow(dead_code)]
|
||||
pub struct StageExecutor;
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Default for StageExecutor {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl StageExecutor {
|
||||
pub fn new() -> Self {
|
||||
|
||||
Reference in New Issue
Block a user