From 6974e3c6d99276cbbe9c1580b0ee534af5173230 Mon Sep 17 00:00:00 2001 From: 4others <44651681+4others@users.noreply.github.com> Date: Wed, 25 Feb 2026 11:36:54 +0400 Subject: [PATCH 1/4] feat: create k8s deploy --- .github/workflows/build-push-image.yaml | 75 +++++++++++++++++++++ .github/workflows/deploy-dev.yaml | 55 +++++++++++++++ .github/workflows/deploy-k8s.yaml | 90 +++++++++++++++++++++++++ .github/workflows/deploy.yml | 41 ----------- .github/workflows/sync-main-to-dev.yaml | 26 +++++++ Dockerfile | 37 ++++++++++ cmd/mcp-server/main.go | 3 + deploy/01_server.yaml | 72 ++++++++++++++++++++ deploy/dev/01_config.yaml | 7 ++ deploy/dev/02_ingress.yaml | 24 +++++++ deploy/prod/01_config.yaml | 7 ++ deploy/prod/02_ingress.yaml | 24 +++++++ scripts/deploy.sh | 54 --------------- 13 files changed, 420 insertions(+), 95 deletions(-) create mode 100644 .github/workflows/build-push-image.yaml create mode 100644 .github/workflows/deploy-dev.yaml create mode 100644 .github/workflows/deploy-k8s.yaml delete mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/sync-main-to-dev.yaml create mode 100644 Dockerfile create mode 100644 deploy/01_server.yaml create mode 100644 deploy/dev/01_config.yaml create mode 100644 deploy/dev/02_ingress.yaml create mode 100644 deploy/prod/01_config.yaml create mode 100644 deploy/prod/02_ingress.yaml delete mode 100755 scripts/deploy.sh diff --git a/.github/workflows/build-push-image.yaml b/.github/workflows/build-push-image.yaml new file mode 100644 index 0000000..6514574 --- /dev/null +++ b/.github/workflows/build-push-image.yaml @@ -0,0 +1,75 @@ +name: Build and Push Docker Image + +on: + workflow_call: + inputs: + service: + required: true + type: string + description: Service name (e.g., mcp-server) + registry: + required: true + type: string + description: Container registry URL + image_name: + required: true + type: string + description: Image name (repository) + tag: + required: true + type: string + description: Image tag + additional_tag: + required: false + type: string + description: Additional tag (e.g., latest, dev-latest) + default: "" + platforms: + required: false + type: string + description: Target platforms (e.g., linux/amd64,linux/arm64) + default: "linux/amd64" + +jobs: + build-and-push: + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ inputs.registry }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Prepare tags + id: prep + run: | + TAGS="${{ inputs.registry }}/${{ inputs.image_name }}/${{ inputs.service }}:${{ inputs.tag }}" + if [ -n "${{ inputs.additional_tag }}" ]; then + TAGS="${TAGS},${{ inputs.registry }}/${{ inputs.image_name }}/${{ inputs.service }}:${{ inputs.additional_tag }}" + fi + echo "tags=${TAGS}" >> $GITHUB_OUTPUT + + - name: Build and push image + uses: docker/build-push-action@v5 + with: + context: . + push: true + platforms: ${{ inputs.platforms }} + tags: ${{ steps.prep.outputs.tags }} + build-args: | + SERVICE=${{ inputs.service }} + cache-from: type=gha + cache-to: type=gha,mode=max + provenance: false diff --git a/.github/workflows/deploy-dev.yaml b/.github/workflows/deploy-dev.yaml new file mode 100644 index 0000000..61771b3 --- /dev/null +++ b/.github/workflows/deploy-dev.yaml @@ -0,0 +1,55 @@ +name: Build and Deploy to Dev + +on: + push: + branches: + - dev + paths-ignore: + - '**.md' + - '.gitignore' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + NS: mcp + +jobs: + build-server: + uses: ./.github/workflows/build-push-image.yaml + permissions: + contents: read + packages: write + with: + service: mcp-server + registry: ghcr.io + image_name: ${{ github.repository }} + tag: dev-${{ github.sha }} + additional_tag: dev-latest + + deploy: + needs: [build-server] + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up kubeconfig + run: | + mkdir -p ~/.kube + echo "${{ secrets.KUBECONFIG_DEV }}" > ~/.kube/config + chmod 600 ~/.kube/config + + - name: Update deployment files + run: | + TAG="dev-${{ github.sha }}" + sed -i "s|image: ghcr.io/${{ env.IMAGE_NAME }}/mcp-server:.*|image: ghcr.io/${{ env.IMAGE_NAME }}/mcp-server:${TAG}|" deploy/01_server.yaml + + - name: Deploy to Kubernetes + run: | + kubectl -n ${{ env.NS }} apply -f deploy/dev + kubectl -n ${{ env.NS }} apply -f deploy/01_server.yaml + kubectl -n ${{ env.NS }} rollout status deployment/server --timeout=300s diff --git a/.github/workflows/deploy-k8s.yaml b/.github/workflows/deploy-k8s.yaml new file mode 100644 index 0000000..62ba351 --- /dev/null +++ b/.github/workflows/deploy-k8s.yaml @@ -0,0 +1,90 @@ +name: Build and Push Images + +on: + release: + types: [published] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + NS: mcp + +jobs: + build-server: + uses: ./.github/workflows/build-push-image.yaml + permissions: + contents: read + packages: write + with: + service: mcp-server + registry: ghcr.io + image_name: ${{ github.repository }} + tag: ${{ github.event.release.tag_name }} + additional_tag: latest + + set-public: + needs: [build-server] + runs-on: ubuntu-latest + permissions: + packages: write + steps: + - name: Set GHCR packages public + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + owner="${{ github.repository_owner }}" + repo="${{ github.event.repository.name }}" + gh api -X PATCH "/orgs/${owner}/packages/container/${repo}%2Fmcp-server/visibility" -f visibility=public || true + + update-manifests: + needs: [build-server, set-public] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: main + + - name: Update deployment files with new image tags + run: | + TAG="${{ github.event.release.tag_name }}" + sed -i "s|image: ghcr.io/${{ env.IMAGE_NAME }}/mcp-server:.*|image: ghcr.io/${{ env.IMAGE_NAME }}/mcp-server:${TAG}|" deploy/01_server.yaml + + - name: Commit and push updated manifests + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add deploy/ + git commit -m "chore: update image tags to ${{ github.event.release.tag_name }}" || echo "No changes to commit" + git push origin main + + deploy: + needs: [update-manifests] + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: main + + - name: Set up kubeconfig + run: | + mkdir -p ~/.kube + echo "${{ secrets.KUBECONFIG_PROD }}" > ~/.kube/config + chmod 600 ~/.kube/config + + - name: Update deployment files + run: | + TAG="${{ github.event.release.tag_name }}" + sed -i "s|image: ghcr.io/${{ env.IMAGE_NAME }}/mcp-server:.*|image: ghcr.io/${{ env.IMAGE_NAME }}/mcp-server:${TAG}|" deploy/01_server.yaml + + - name: Deploy to Kubernetes + run: | + kubectl -n ${{ env.NS }} apply -f deploy/prod + kubectl -n ${{ env.NS }} apply -f deploy/01_server.yaml + kubectl -n ${{ env.NS }} rollout status deployment/server --timeout=300s diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 6f00328..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Deploy - -on: - push: - branches: [main] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - cache: true - - - name: Vet - run: go vet ./... - - - name: Test - run: go test ./... -count=1 -timeout 120s - - deploy: - needs: test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Deploy to production - env: - SSH_PRIVATE_KEY: ${{ secrets.SSH_KEY }} - SERVER: ${{ secrets.SSH_SERVER }} - USER: ${{ secrets.SSH_USER }} - DEPLOY_PATH: ${{ secrets.SSH_PATH }} - run: | - mkdir -p ~/.ssh - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan $SERVER >> ~/.ssh/known_hosts - ./scripts/deploy.sh diff --git a/.github/workflows/sync-main-to-dev.yaml b/.github/workflows/sync-main-to-dev.yaml new file mode 100644 index 0000000..7f6e0c4 --- /dev/null +++ b/.github/workflows/sync-main-to-dev.yaml @@ -0,0 +1,26 @@ +name: Sync main to dev + +on: + push: + branches: + - main + +jobs: + sync-main-to-dev: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Sync main to dev + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git fetch origin dev:dev + git checkout dev + git merge origin/main --no-edit + git push origin dev diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bdbe427 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +FROM golang:1.25.5 AS builder + +ARG TARGETARCH=amd64 + +RUN apt-get update && apt-get install -y clang lld wget + +RUN wget https://github.com/vultisig/go-wrappers/archive/refs/heads/master.tar.gz && \ + tar -xzf master.tar.gz && \ + cd go-wrappers-master && \ + mkdir -p /usr/local/lib/dkls/includes && \ + cp includes/go-dkls.h includes/go-schnorr.h /usr/local/lib/dkls/includes/ && \ + if [ ! -d "includes/linux-${TARGETARCH}" ]; then echo "Error: includes/linux-${TARGETARCH} not found" && exit 1; fi && \ + cp -r includes/linux-${TARGETARCH} /usr/local/lib/dkls/includes/linux + +ARG SERVICE +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download +COPY . . + +ENV CGO_ENABLED=1 +ENV CC=clang +ENV CGO_LDFLAGS=-fuse-ld=lld +ENV LD_LIBRARY_PATH=/usr/local/lib/dkls/includes/linux:$LD_LIBRARY_PATH +RUN go build -o main cmd/${SERVICE}/main.go + +FROM ubuntu:22.04 + +RUN apt-get update && apt-get install -y \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY --from=builder /app/main . +COPY --from=builder /usr/local/lib/dkls /usr/local/lib/dkls +ENV LD_LIBRARY_PATH=/usr/local/lib/dkls/includes/linux:$LD_LIBRARY_PATH diff --git a/cmd/mcp-server/main.go b/cmd/mcp-server/main.go index f6807bd..604a2a6 100644 --- a/cmd/mcp-server/main.go +++ b/cmd/mcp-server/main.go @@ -66,6 +66,9 @@ func main() { mcpHandler := server.NewStreamableHTTPServer(s) mux := http.NewServeMux() + mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) mux.Handle("/mcp", mcpHandler) skillHandler := skills.NewHandler(logger) mux.Handle("/skills", skillHandler) diff --git a/deploy/01_server.yaml b/deploy/01_server.yaml new file mode 100644 index 0000000..2cf22e9 --- /dev/null +++ b/deploy/01_server.yaml @@ -0,0 +1,72 @@ +apiVersion: v1 +kind: Service +metadata: + name: server + labels: + app: server +spec: + selector: + app: server + ports: + - name: http + port: 80 + targetPort: 80 + protocol: TCP + type: ClusterIP +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: server + labels: + app: server +spec: + replicas: 1 + selector: + matchLabels: + app: server + template: + metadata: + labels: + app: server + spec: + imagePullSecrets: + - name: ghcr + containers: + - name: server + image: ghcr.io/vultisig/mcp/mcp-server:latest + command: ["/app/main", "-http", ":80"] + ports: + - containerPort: 80 + name: http + env: + - name: ETH_RPC_URL + valueFrom: + configMapKeyRef: + name: mcp-config + key: eth-rpc-url + - name: BLOCKCHAIR_API_URL + valueFrom: + configMapKeyRef: + name: mcp-config + key: blockchair-api-url + - name: COINGECKO_API_KEY + valueFrom: + secretKeyRef: + name: mcp-secrets + key: coingecko-api-key + resources: + requests: + memory: "32Mi" + cpu: "50m" + limits: + memory: "512Mi" + cpu: "500m" + readinessProbe: + httpGet: + path: /healthz + port: 80 + livenessProbe: + httpGet: + path: /healthz + port: 80 diff --git a/deploy/dev/01_config.yaml b/deploy/dev/01_config.yaml new file mode 100644 index 0000000..a64e357 --- /dev/null +++ b/deploy/dev/01_config.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: mcp-config +data: + eth-rpc-url: "https://ethereum-rpc.publicnode.com" + blockchair-api-url: "https://api.vultisig.com/blockchair" diff --git a/deploy/dev/02_ingress.yaml b/deploy/dev/02_ingress.yaml new file mode 100644 index 0000000..ea39259 --- /dev/null +++ b/deploy/dev/02_ingress.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: mcp + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + cert-manager.io/cluster-issuer: "letsencrypt" +spec: + ingressClassName: traefik + tls: + - secretName: mcp-tls + hosts: + - mcp.dev.plugins.vultisig.com + rules: + - host: mcp.dev.plugins.vultisig.com + http: + paths: + - path: / + pathType: ImplementationSpecific + backend: + service: + name: server + port: + number: 80 diff --git a/deploy/prod/01_config.yaml b/deploy/prod/01_config.yaml new file mode 100644 index 0000000..a64e357 --- /dev/null +++ b/deploy/prod/01_config.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: mcp-config +data: + eth-rpc-url: "https://ethereum-rpc.publicnode.com" + blockchair-api-url: "https://api.vultisig.com/blockchair" diff --git a/deploy/prod/02_ingress.yaml b/deploy/prod/02_ingress.yaml new file mode 100644 index 0000000..6698dd2 --- /dev/null +++ b/deploy/prod/02_ingress.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: mcp + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + cert-manager.io/cluster-issuer: "letsencrypt" +spec: + ingressClassName: traefik + tls: + - secretName: mcp-tls + hosts: + - mcp.prod.plugins.vultisig.com + rules: + - host: mcp.prod.plugins.vultisig.com + http: + paths: + - path: / + pathType: ImplementationSpecific + backend: + service: + name: server + port: + number: 80 diff --git a/scripts/deploy.sh b/scripts/deploy.sh deleted file mode 100755 index 39955e3..0000000 --- a/scripts/deploy.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash - -set -e - -if [ -z "$SERVER" ] || [ -z "$USER" ] || [ -z "$DEPLOY_PATH" ]; then - echo "Error: SERVER, USER, and DEPLOY_PATH environment variables must be set" - exit 1 -fi - -echo "Deploying to $USER@$SERVER:$DEPLOY_PATH..." - -echo "1. Syncing files to server..." -rsync -avz --delete \ - --exclude='.git' \ - --exclude='.devenv' \ - --exclude='.env' \ - --exclude='*.log' \ - --exclude='.github/' \ - ./ $USER@$SERVER:$DEPLOY_PATH/ - -echo "2. Building and deploying on server..." -ssh $USER@$SERVER << EOF -set -e -export PATH=\$PATH:/usr/local/go/bin - -cd $DEPLOY_PATH - -echo "Building mcp-server binary..." -go build -o mcp-server ./cmd/mcp-server/ - -echo "Stopping service before binary replacement..." -sudo systemctl stop mcp || true - -echo "Installing binary to /usr/local/bin/..." -sudo cp mcp-server /usr/local/bin/ -sudo chmod +x /usr/local/bin/mcp-server - -if [ ! -f "/usr/local/bin/mcp-server" ]; then - echo "ERROR: mcp-server binary not found in /usr/local/bin/" - exit 1 -fi - -echo "Binary installation successful:" -ls -la /usr/local/bin/mcp-server - -echo "Restarting mcp service..." -sudo systemctl restart mcp - -echo "Checking service status..." -sleep 2 -sudo systemctl status mcp --no-pager -l -EOF - -echo "Deployment finished successfully!" From b296f8b35c8aee638a12c9cf8e786238b5a40682 Mon Sep 17 00:00:00 2001 From: 4others <44651681+4others@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:24:15 +0400 Subject: [PATCH 2/4] adjust based on review --- .github/workflows/deploy-dev.yaml | 2 +- .github/workflows/{deploy-k8s.yaml => deploy-prod.yaml} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename .github/workflows/{deploy-k8s.yaml => deploy-prod.yaml} (99%) diff --git a/.github/workflows/deploy-dev.yaml b/.github/workflows/deploy-dev.yaml index 61771b3..0326e3a 100644 --- a/.github/workflows/deploy-dev.yaml +++ b/.github/workflows/deploy-dev.yaml @@ -11,7 +11,7 @@ on: env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} - NS: mcp + NS: agent jobs: build-server: diff --git a/.github/workflows/deploy-k8s.yaml b/.github/workflows/deploy-prod.yaml similarity index 99% rename from .github/workflows/deploy-k8s.yaml rename to .github/workflows/deploy-prod.yaml index 62ba351..baaf027 100644 --- a/.github/workflows/deploy-k8s.yaml +++ b/.github/workflows/deploy-prod.yaml @@ -7,7 +7,7 @@ on: env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} - NS: mcp + NS: agent jobs: build-server: From b1df379a9acef4e5d13e9ca0b01aa55a9a687fa2 Mon Sep 17 00:00:00 2001 From: 4others <44651681+4others@users.noreply.github.com> Date: Thu, 26 Feb 2026 21:11:16 +0400 Subject: [PATCH 3/4] modify coingecko approach --- .env.example | 3 --- CLAUDE.md | 5 ++--- cmd/mcp-server/main.go | 2 +- deploy/01_server.yaml | 5 ----- internal/coingecko/client.go | 14 ++++---------- internal/coingecko/client_test.go | 20 +++++--------------- internal/config/config.go | 9 ++++----- 7 files changed, 16 insertions(+), 42 deletions(-) diff --git a/.env.example b/.env.example index e1ca054..96a4a6a 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,5 @@ # Ethereum JSON-RPC endpoint (default: https://ethereum-rpc.publicnode.com) ETH_RPC_URL= -# CoinGecko API key (optional, raises rate limits for token discovery) -COINGECKO_API_KEY= - # Blockchair proxy base URL (default: https://api.vultisig.com/blockchair) BLOCKCHAIR_API_URL= diff --git a/CLAUDE.md b/CLAUDE.md index 2d5beca..34f2fa0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,7 +32,6 @@ Config uses `github.com/kelseyhightower/envconfig`. All EVM RPC URLs default to | `EVM_BLAST_URL` | `https://blast-rpc.publicnode.com` | Blast endpoint | | `EVM_MANTLE_URL` | `https://mantle-rpc.publicnode.com` | Mantle endpoint | | `EVM_ZKSYNC_URL` | `https://mainnet.era.zksync.io` | zkSync Era endpoint | -| `COINGECKO_API_KEY` | (empty) | CoinGecko API key (optional, raises rate limits) | | `BLOCKCHAIR_API_URL` | `https://api.vultisig.com/blockchair` | Blockchair proxy base URL for UTXO chain queries | | `THORCHAIN_URL` | `https://thornode.ninerealms.com` | THORChain node base URL for fee rates | | `SOLANA_RPC_URL` | `https://api.mainnet-beta.solana.com` | Solana JSON-RPC endpoint | @@ -64,8 +63,8 @@ internal/tools/ search_token.go # Token discovery via CoinGecko API abi_encode.go # ABI encode function calls / raw args abi_decode.go # ABI decode output data - convert_amount.go - registry.go + convert_amount.go + registry.go internal/coingecko/client.go # CoinGecko REST API client internal/blockchair/client.go # Blockchair UTXO chain API client (via Vultisig proxy) internal/thorchain/client.go # THORChain node client (fee rates via inbound_addresses) diff --git a/cmd/mcp-server/main.go b/cmd/mcp-server/main.go index 604a2a6..e20e69f 100644 --- a/cmd/mcp-server/main.go +++ b/cmd/mcp-server/main.go @@ -38,7 +38,7 @@ func main() { defer evmPool.Close() store := vault.NewStore() - cgClient := coingecko.NewClient(cfg.CoinGeckoAPIKey) + cgClient := coingecko.NewClient() bcClient := blockchair.NewClient(cfg.BlockchairURL) hooks := mcplog.NewHooks(logger) diff --git a/deploy/01_server.yaml b/deploy/01_server.yaml index 2cf22e9..60c096f 100644 --- a/deploy/01_server.yaml +++ b/deploy/01_server.yaml @@ -50,11 +50,6 @@ spec: configMapKeyRef: name: mcp-config key: blockchair-api-url - - name: COINGECKO_API_KEY - valueFrom: - secretKeyRef: - name: mcp-secrets - key: coingecko-api-key resources: requests: memory: "32Mi" diff --git a/internal/coingecko/client.go b/internal/coingecko/client.go index 26e391b..2a6c238 100644 --- a/internal/coingecko/client.go +++ b/internal/coingecko/client.go @@ -10,7 +10,7 @@ import ( ) const ( - defaultBaseURL = "https://api.coingecko.com/api/v3" + defaultBaseURL = "https://api.vultisig.com/coingeicko/api/v3" // searchCacheTTL controls how long search results are reused. searchCacheTTL = 5 * time.Minute @@ -20,23 +20,20 @@ const ( detailCacheTTL = 10 * time.Minute ) -// Client wraps the CoinGecko REST API with an in-memory TTL cache. +// Client wraps the CoinGecko REST API (via Vultisig proxy) with an in-memory TTL cache. type Client struct { http *http.Client baseURL string - apiKey string searchCache *ttlCache[[]SearchCoin] detailCache *ttlCache[*CoinDetail] } -// NewClient creates a CoinGecko API client. If apiKey is non-empty it is -// sent as a demo API key header to raise rate limits. -func NewClient(apiKey string) *Client { +// NewClient creates a CoinGecko API client that routes through the Vultisig proxy. +func NewClient() *Client { return &Client{ http: &http.Client{Timeout: 30 * time.Second}, baseURL: defaultBaseURL, - apiKey: apiKey, searchCache: newTTLCache[[]SearchCoin](searchCacheTTL), detailCache: newTTLCache[*CoinDetail](detailCacheTTL), } @@ -47,9 +44,6 @@ func (c *Client) doGet(ctx context.Context, path string) (*http.Response, error) if err != nil { return nil, err } - if c.apiKey != "" { - req.Header.Set("x-cg-demo-api-key", c.apiKey) - } return c.http.Do(req) } diff --git a/internal/coingecko/client_test.go b/internal/coingecko/client_test.go index 9672ab4..3604ff5 100644 --- a/internal/coingecko/client_test.go +++ b/internal/coingecko/client_test.go @@ -2,21 +2,11 @@ package coingecko import ( "context" - "os" "testing" ) -func apiKey(t *testing.T) string { - t.Helper() - key := os.Getenv("COINGECKO_API_KEY") - if key == "" { - t.Skip("COINGECKO_API_KEY not set") - } - return key -} - func TestSearch(t *testing.T) { - c := NewClient(apiKey(t)) + c := NewClient() coins, err := c.Search(context.Background(), "USDC") if err != nil { t.Fatalf("Search: %v", err) @@ -41,7 +31,7 @@ func TestSearch(t *testing.T) { } func TestCoinDetail_Token(t *testing.T) { - c := NewClient(apiKey(t)) + c := NewClient() detail, err := c.CoinDetail(context.Background(), "usd-coin") if err != nil { t.Fatalf("CoinDetail: %v", err) @@ -67,7 +57,7 @@ func TestCoinDetail_Token(t *testing.T) { } func TestCoinDetail_NativeAsset(t *testing.T) { - c := NewClient(apiKey(t)) + c := NewClient() detail, err := c.CoinDetail(context.Background(), "bitcoin") if err != nil { t.Fatalf("CoinDetail: %v", err) @@ -89,7 +79,7 @@ func TestCoinDetail_NativeAsset(t *testing.T) { } func TestSearch_Cache(t *testing.T) { - c := NewClient(apiKey(t)) + c := NewClient() ctx := context.Background() // First call populates cache. @@ -110,7 +100,7 @@ func TestSearch_Cache(t *testing.T) { } func TestCoinDetail_Cache(t *testing.T) { - c := NewClient(apiKey(t)) + c := NewClient() ctx := context.Background() d1, err := c.CoinDetail(ctx, "ethereum") diff --git a/internal/config/config.go b/internal/config/config.go index aafe126..eb8a427 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -30,11 +30,10 @@ type RPCItem struct { } type Config struct { - EVM EVMRPCConfig - CoinGeckoAPIKey string `envconfig:"COINGECKO_API_KEY"` - BlockchairURL string `envconfig:"BLOCKCHAIR_API_URL" default:"https://api.vultisig.com/blockchair"` - ThorchainURL string `envconfig:"THORCHAIN_URL" default:"https://thornode.ninerealms.com"` - SolanaRPCURL string `envconfig:"SOLANA_RPC_URL" default:"https://api.mainnet-beta.solana.com"` + EVM EVMRPCConfig + BlockchairURL string `envconfig:"BLOCKCHAIR_API_URL" default:"https://api.vultisig.com/blockchair"` + ThorchainURL string `envconfig:"THORCHAIN_URL" default:"https://thornode.ninerealms.com"` + SolanaRPCURL string `envconfig:"SOLANA_RPC_URL" default:"https://api.mainnet-beta.solana.com"` } // ToURLMap converts the EVM RPC config to a chain-name → URL map, From 7860f9835fc2e3323209d8684138bf11f8db2ddb Mon Sep 17 00:00:00 2001 From: 4others <44651681+4others@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:17:35 +0400 Subject: [PATCH 4/4] adjust according to review --- deploy/01_server.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/deploy/01_server.yaml b/deploy/01_server.yaml index 60c096f..b5402e1 100644 --- a/deploy/01_server.yaml +++ b/deploy/01_server.yaml @@ -30,8 +30,6 @@ spec: labels: app: server spec: - imagePullSecrets: - - name: ghcr containers: - name: server image: ghcr.io/vultisig/mcp/mcp-server:latest @@ -40,7 +38,7 @@ spec: - containerPort: 80 name: http env: - - name: ETH_RPC_URL + - name: EVM_ETHEREUM_URL valueFrom: configMapKeyRef: name: mcp-config