diff --git a/.github/workflows/cicd-stg.yaml b/.github/workflows/cicd-stg.yaml new file mode 100644 index 0000000..29d52b5 --- /dev/null +++ b/.github/workflows/cicd-stg.yaml @@ -0,0 +1,47 @@ +name: "CICD staging" + +on: + pull_request: + branches: + - main + push: + branches: + - main + - zilliqa + +jobs: + build: + permissions: + id-token: write + contents: write + runs-on: ubuntu-22.04 + if: github.actor != 'dependabot[bot]' + name: "Build image" + + env: + REGISTRY: ${{ secrets.REGISTRY }} + IMAGE_NAME: ${{ secrets.REGISTRY }}/${{ secrets.GCP_PROJECT_ID_STG }}/zilliqa-public/evm-mcp-server + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + - name: Docker build and push in production + uses: Zilliqa/gh-actions-workflows/actions/ci-dockerized-app-build-push@v2 + with: + context: . + push: ${{ github.ref_name == github.event.repository.default_branch || github.ref_name == 'zilliqa' }} + tag: ${{ env.IMAGE_NAME }} + tag-length: 8 + tag-latest: false + registry: ${{ env.REGISTRY }} + workload-identity-provider: "${{ secrets.GCP_PRD_GITHUB_WIF }}" + service-account: "${{ secrets.GCP_STG_GITHUB_SA_DOCKER_REGISTRY }}" + cache-key: ${{ env.IMAGE_NAME }}-cache diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f09bc96 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +# Use Bun as the base image (preferred runtime according to README) +FROM oven/bun:1.1.29-alpine AS base + +# Set working directory +WORKDIR /app + +# Copy package files for dependency installation +COPY package.json bun.lockb* ./ + +# Install dependencies (including dev dependencies for TypeScript compilation) +RUN bun install --frozen-lockfile + +# Install curl for health checks +RUN apk add --no-cache curl + +# Copy source code +COPY . . + +# Build the project for production +RUN bun run build && bun run build:http + +# Create a non-root user for security +RUN addgroup -g 1001 -S nodejs && \ + adduser -S evm-mcp -u 1001 + +# Change ownership of the app directory +RUN chown -R evm-mcp:nodejs /app +USER evm-mcp + +# Expose the HTTP server port (default: 3000) +EXPOSE 3000 + +# Health check for HTTP server with streaming endpoints +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:3000/health || exit 1 + +# Run the HTTP server with streaming support +CMD ["bun", "start:http"] + +# Multi-stage build for production optimization +FROM base AS production + +# Labels for better container management +LABEL maintainer="EVM MCP Server" +LABEL description="HTTP MCP Server for EVM blockchain interactions with streaming support" +LABEL version="1.0.0" + +# Environment variables for HTTP server configuration +ENV NODE_ENV=production +ENV PORT=3000 +ENV HOST=0.0.0.0 \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..aaf76c3 --- /dev/null +++ b/Makefile @@ -0,0 +1,116 @@ +# Makefile for EVM MCP Server Docker operations + +# Variables +IMAGE_NAME = evm-mcp-server +IMAGE_TAG = latest +CONTAINER_NAME = evm-mcp-server +PORT = 3000 + +# Default target +.DEFAULT_GOAL := help + +# Help target +.PHONY: help +help: ## Show this help message + @echo "Available commands:" + @echo "" + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +# Build targets +.PHONY: build +build: ## Build the Docker image + docker build -t $(IMAGE_NAME):$(IMAGE_TAG) . + +.PHONY: build-no-cache +build-no-cache: ## Build the Docker image without cache + docker build --no-cache -t $(IMAGE_NAME):$(IMAGE_TAG) . + +.PHONY: build-production +build-production: ## Build the production Docker image + docker build --target production -t $(IMAGE_NAME):$(IMAGE_TAG) . + +# Run targets +.PHONY: run +run: ## Run the Docker container on port 3000 + docker run -d --name $(CONTAINER_NAME) -p $(PORT):3000 $(IMAGE_NAME):$(IMAGE_TAG) + +.PHONY: run-interactive +run-interactive: ## Run the Docker container interactively + docker run -it --rm -p $(PORT):3000 $(IMAGE_NAME):$(IMAGE_TAG) + +.PHONY: run-detached +run-detached: ## Run the Docker container in detached mode + docker run -d --name $(CONTAINER_NAME) -p $(PORT):3000 $(IMAGE_NAME):$(IMAGE_TAG) + +# Stop and clean targets +.PHONY: stop +stop: ## Stop the running container + docker stop $(CONTAINER_NAME) || true + +.PHONY: remove +remove: ## Remove the container + docker rm $(CONTAINER_NAME) || true + +.PHONY: clean +clean: ## Stop and remove the container + $(MAKE) stop + $(MAKE) remove + +.PHONY: clean-images +clean-images: ## Remove Docker images + docker rmi $(IMAGE_NAME):$(IMAGE_TAG) || true + +# Development targets +.PHONY: dev +dev: ## Build and run for development (interactive) + $(MAKE) build + $(MAKE) run-interactive + +.PHONY: dev-detached +dev-detached: ## Build and run for development (detached) + $(MAKE) build + $(MAKE) run-detached + +# Production targets +.PHONY: prod +prod: ## Build production image and run + $(MAKE) build-production + $(MAKE) run-detached + +# Utility targets +.PHONY: logs +logs: ## Show container logs + docker logs -f $(CONTAINER_NAME) + +.PHONY: shell +shell: ## Access container shell + docker exec -it $(CONTAINER_NAME) /bin/sh + +.PHONY: status +status: ## Show container status + docker ps -a | grep $(CONTAINER_NAME) || echo "Container not found" + +.PHONY: health +health: ## Check container health + curl -f http://localhost:$(PORT)/health || echo "Health check failed" + +# Complete workflow targets +.PHONY: start +start: ## Complete workflow: build and run + $(MAKE) build + $(MAKE) run-detached + @echo "Container started on http://localhost:$(PORT) (HTTP/1.1 with HTTP/2 upgrade support)" + @echo "Health check: http://localhost:$(PORT)/health" + @echo "StreamableHTTP endpoint: http://localhost:$(PORT)/mcp" + +.PHONY: restart +restart: ## Restart the container + $(MAKE) clean + $(MAKE) start + +.PHONY: rebuild +rebuild: ## Rebuild and restart the container + $(MAKE) clean + $(MAKE) build-no-cache + $(MAKE) run-detached + @echo "Container rebuilt and started on http://localhost:$(PORT)" diff --git a/README.md b/README.md index bf74039..f845bfa 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ npm install The server uses the following default configuration: - **Default Chain ID**: 1 (Ethereum Mainnet) -- **Server Port**: 3001 +- **Server Port**: 3000 - **Server Host**: 0.0.0.0 (accessible from any network interface) These values are hardcoded in the application. If you need to modify them, you can edit the following files: @@ -271,7 +271,7 @@ If you're developing a web application and want to connect to the HTTP server wi { "mcpServers": { "evm-mcp-sse": { - "url": "http://localhost:3001/sse" + "url": "http://localhost:3000/sse" } } } diff --git a/bun.lock b/bun.lock index 40cb91b..0c8fe8c 100644 --- a/bun.lock +++ b/bun.lock @@ -4,16 +4,18 @@ "": { "name": "mcp-evm-server", "dependencies": { - "@modelcontextprotocol/sdk": "^1.6.1", + "@modelcontextprotocol/sdk": "^1.7.0", + "@zilliqa-js/crypto": "^3.5.0", "cors": "^2.8.5", "express": "^4.21.2", - "viem": "^2.23.8", + "viem": "^2.23.9", "zod": "^3.24.2", }, "devDependencies": { "@types/bun": "latest", "@types/cors": "^2.8.17", "@types/express": "^5.0.0", + "conventional-changelog-cli": "^5.0.0", }, "peerDependencies": { "typescript": "^5.8.2", @@ -23,6 +25,14 @@ "packages": { "@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.0", "", {}, "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg=="], + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], + + "@conventional-changelog/git-client": ["@conventional-changelog/git-client@1.0.1", "", { "dependencies": { "@types/semver": "^7.5.5", "semver": "^7.5.2" }, "peerDependencies": { "conventional-commits-filter": "^5.0.0", "conventional-commits-parser": "^6.0.0" }, "optionalPeers": ["conventional-commits-filter", "conventional-commits-parser"] }, "sha512-PJEqBwAleffCMETaVm/fUgHldzBE35JFk3/9LL6NUA5EXa3qednu+UT6M7E5iBu3zIQZCULYIiZ90fBYHt6xUw=="], + + "@hutson/parse-repository-url": ["@hutson/parse-repository-url@5.0.0", "", {}, "sha512-e5+YUKENATs1JgYHMzTr2MW/NDcXGfYFAuOQU8gJgF/kEh4EqKgfGrfLI67bMD4tbhZVlkigz/9YYwWcbOFthg=="], + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.7.0", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-IYPe/FLpvF3IZrd/f5p5ffmWhMc3aEMuM2wGJASDqC2Ge7qatVCdbfPx3n/5xFeb19xN0j/911M2AaFuircsWA=="], "@noble/curves": ["@noble/curves@1.8.1", "", { "dependencies": { "@noble/hashes": "1.7.1" } }, "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ=="], @@ -53,52 +63,128 @@ "@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="], + "@types/normalize-package-data": ["@types/normalize-package-data@2.4.4", "", {}, "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="], + "@types/qs": ["@types/qs@6.9.18", "", {}, "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA=="], "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], + "@types/semver": ["@types/semver@7.7.0", "", {}, "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA=="], + "@types/send": ["@types/send@0.17.4", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA=="], "@types/serve-static": ["@types/serve-static@1.15.7", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "*" } }, "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw=="], "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], + "@zilliqa-js/crypto": ["@zilliqa-js/crypto@3.5.0", "", { "dependencies": { "@zilliqa-js/util": "3.5.0", "aes-js": "^3.1.1", "buffer": "^6.0.3", "crypto-js": "^4.2.0", "elliptic": "^6.5.0", "hash.js": "^1.1.5", "hmac-drbg": "^1.0.1", "pbkdf2": "^3.0.16", "scrypt-js": "^3.0.1", "scryptsy": "^2.1.0", "tslib": "2.3.1", "uuid": "8.3.2" } }, "sha512-KMTY4hREh706k0oqCJ7KTFCEgPvgWuckv7z1SkOc9UDjJnnfOD8KxGWrleaKMZOw+EjKJRybxgewPUvSZ+o7Mw=="], + + "@zilliqa-js/util": ["@zilliqa-js/util@3.5.0", "", { "dependencies": { "bn.js": "^4.11.8", "camelcase": "^5.0.0", "long": "^4.0.0", "tslib": "2.3.1" } }, "sha512-YT8OhYAv2nCIrRTMMwXLDEqyV/O0jbtfc5Uvlb0qkIx56a4OeneebIJtBlTwf9ld7MZlU5LvvDOEJyljQErz6w=="], + "abitype": ["abitype@1.0.8", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg=="], "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], + "add-stream": ["add-stream@1.0.0", "", {}, "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ=="], + + "aes-js": ["aes-js@3.1.2", "", {}, "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ=="], + "array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="], + "array-ify": ["array-ify@1.0.0", "", {}, "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng=="], + + "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="], + "body-parser": ["body-parser@1.20.3", "", { "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" } }, "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g=="], + "brorand": ["brorand@1.1.0", "", {}, "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w=="], + + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + "bun-types": ["bun-types@1.2.5", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-3oO6LVGGRRKI4kHINx5PIdIgnLRb7l/SprhzqXapmoYkFl5m4j6EvALvbDVuuBFaamB46Ap6HCUxIXNLCGy+tg=="], "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + "camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + + "cipher-base": ["cipher-base@1.0.6", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1" } }, "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw=="], + + "compare-func": ["compare-func@2.0.0", "", { "dependencies": { "array-ify": "^1.0.0", "dot-prop": "^5.1.0" } }, "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA=="], + "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + "conventional-changelog": ["conventional-changelog@6.0.0", "", { "dependencies": { "conventional-changelog-angular": "^8.0.0", "conventional-changelog-atom": "^5.0.0", "conventional-changelog-codemirror": "^5.0.0", "conventional-changelog-conventionalcommits": "^8.0.0", "conventional-changelog-core": "^8.0.0", "conventional-changelog-ember": "^5.0.0", "conventional-changelog-eslint": "^6.0.0", "conventional-changelog-express": "^5.0.0", "conventional-changelog-jquery": "^6.0.0", "conventional-changelog-jshint": "^5.0.0", "conventional-changelog-preset-loader": "^5.0.0" } }, "sha512-tuUH8H/19VjtD9Ig7l6TQRh+Z0Yt0NZ6w/cCkkyzUbGQTnUEmKfGtkC9gGfVgCfOL1Rzno5NgNF4KY8vR+Jo3w=="], + + "conventional-changelog-angular": ["conventional-changelog-angular@8.0.0", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA=="], + + "conventional-changelog-atom": ["conventional-changelog-atom@5.0.0", "", {}, "sha512-WfzCaAvSCFPkznnLgLnfacRAzjgqjLUjvf3MftfsJzQdDICqkOOpcMtdJF3wTerxSpv2IAAjX8doM3Vozqle3g=="], + + "conventional-changelog-cli": ["conventional-changelog-cli@5.0.0", "", { "dependencies": { "add-stream": "^1.0.0", "conventional-changelog": "^6.0.0", "meow": "^13.0.0", "tempfile": "^5.0.0" }, "bin": { "conventional-changelog": "cli.js" } }, "sha512-9Y8fucJe18/6ef6ZlyIlT2YQUbczvoQZZuYmDLaGvcSBP+M6h+LAvf7ON7waRxKJemcCII8Yqu5/8HEfskTxJQ=="], + + "conventional-changelog-codemirror": ["conventional-changelog-codemirror@5.0.0", "", {}, "sha512-8gsBDI5Y3vrKUCxN6Ue8xr6occZ5nsDEc4C7jO/EovFGozx8uttCAyfhRrvoUAWi2WMm3OmYs+0mPJU7kQdYWQ=="], + + "conventional-changelog-conventionalcommits": ["conventional-changelog-conventionalcommits@8.0.0", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-eOvlTO6OcySPyyyk8pKz2dP4jjElYunj9hn9/s0OB+gapTO8zwS9UQWrZ1pmF2hFs3vw1xhonOLGcGjy/zgsuA=="], + + "conventional-changelog-core": ["conventional-changelog-core@8.0.0", "", { "dependencies": { "@hutson/parse-repository-url": "^5.0.0", "add-stream": "^1.0.0", "conventional-changelog-writer": "^8.0.0", "conventional-commits-parser": "^6.0.0", "git-raw-commits": "^5.0.0", "git-semver-tags": "^8.0.0", "hosted-git-info": "^7.0.0", "normalize-package-data": "^6.0.0", "read-package-up": "^11.0.0", "read-pkg": "^9.0.0" } }, "sha512-EATUx5y9xewpEe10UEGNpbSHRC6cVZgO+hXQjofMqpy+gFIrcGvH3Fl6yk2VFKh7m+ffenup2N7SZJYpyD9evw=="], + + "conventional-changelog-ember": ["conventional-changelog-ember@5.0.0", "", {}, "sha512-RPflVfm5s4cSO33GH/Ey26oxhiC67akcxSKL8CLRT3kQX2W3dbE19sSOM56iFqUJYEwv9mD9r6k79weWe1urfg=="], + + "conventional-changelog-eslint": ["conventional-changelog-eslint@6.0.0", "", {}, "sha512-eiUyULWjzq+ybPjXwU6NNRflApDWlPEQEHvI8UAItYW/h22RKkMnOAtfCZxMmrcMO1OKUWtcf2MxKYMWe9zJuw=="], + + "conventional-changelog-express": ["conventional-changelog-express@5.0.0", "", {}, "sha512-D8Q6WctPkQpvr2HNCCmwU5GkX22BVHM0r4EW8vN0230TSyS/d6VQJDAxGb84lbg0dFjpO22MwmsikKL++Oo/oQ=="], + + "conventional-changelog-jquery": ["conventional-changelog-jquery@6.0.0", "", {}, "sha512-2kxmVakyehgyrho2ZHBi90v4AHswkGzHuTaoH40bmeNqUt20yEkDOSpw8HlPBfvEQBwGtbE+5HpRwzj6ac2UfA=="], + + "conventional-changelog-jshint": ["conventional-changelog-jshint@5.0.0", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-gGNphSb/opc76n2eWaO6ma4/Wqu3tpa2w7i9WYqI6Cs2fncDSI2/ihOfMvXveeTTeld0oFvwMVNV+IYQIk3F3g=="], + + "conventional-changelog-preset-loader": ["conventional-changelog-preset-loader@5.0.0", "", {}, "sha512-SetDSntXLk8Jh1NOAl1Gu5uLiCNSYenB5tm0YVeZKePRIgDW9lQImromTwLa3c/Gae298tsgOM+/CYT9XAl0NA=="], + + "conventional-changelog-writer": ["conventional-changelog-writer@8.2.0", "", { "dependencies": { "conventional-commits-filter": "^5.0.0", "handlebars": "^4.7.7", "meow": "^13.0.0", "semver": "^7.5.2" }, "bin": { "conventional-changelog-writer": "dist/cli/index.js" } }, "sha512-Y2aW4596l9AEvFJRwFGJGiQjt2sBYTjPD18DdvxX9Vpz0Z7HQ+g1Z+6iYDAm1vR3QOJrDBkRHixHK/+FhkR6Pw=="], + + "conventional-commits-filter": ["conventional-commits-filter@5.0.0", "", {}, "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q=="], + + "conventional-commits-parser": ["conventional-commits-parser@6.2.0", "", { "dependencies": { "meow": "^13.0.0" }, "bin": { "conventional-commits-parser": "dist/cli/index.js" } }, "sha512-uLnoLeIW4XaoFtH37qEcg/SXMJmKF4vi7V0H2rnPueg+VEtFGA/asSCNTcq4M/GQ6QmlzchAEtOoDTtKqWeHag=="], + "cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="], "cookie-signature": ["cookie-signature@1.0.6", "", {}, "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="], "cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="], + "create-hash": ["create-hash@1.1.3", "", { "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", "ripemd160": "^2.0.0", "sha.js": "^2.4.0" } }, "sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA=="], + + "create-hmac": ["create-hmac@1.1.7", "", { "dependencies": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", "inherits": "^2.0.1", "ripemd160": "^2.0.0", "safe-buffer": "^5.0.1", "sha.js": "^2.4.8" } }, "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg=="], + + "crypto-js": ["crypto-js@4.2.0", "", {}, "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="], + "debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], "destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="], + "dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + "elliptic": ["elliptic@6.6.1", "", { "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", "hash.js": "^1.0.0", "hmac-drbg": "^1.0.1", "inherits": "^2.0.4", "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g=="], + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], @@ -123,6 +209,10 @@ "finalhandler": ["finalhandler@1.3.1", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", "statuses": "2.0.1", "unpipe": "~1.0.0" } }, "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ=="], + "find-up-simple": ["find-up-simple@1.0.1", "", {}, "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ=="], + + "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], @@ -133,28 +223,66 @@ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + "git-raw-commits": ["git-raw-commits@5.0.0", "", { "dependencies": { "@conventional-changelog/git-client": "^1.0.0", "meow": "^13.0.0" }, "bin": { "git-raw-commits": "src/cli.js" } }, "sha512-I2ZXrXeOc0KrCvC7swqtIFXFN+rbjnC7b2T943tvemIOVNl+XP8YnA9UVwqFhzzLClnSA60KR/qEjLpXzs73Qg=="], + + "git-semver-tags": ["git-semver-tags@8.0.0", "", { "dependencies": { "@conventional-changelog/git-client": "^1.0.0", "meow": "^13.0.0" }, "bin": { "git-semver-tags": "src/cli.js" } }, "sha512-N7YRIklvPH3wYWAR2vysaqGLPRcpwQ0GKdlqTiVN5w1UmCdaeY3K8s6DMKRCh54DDdzyt/OAB6C8jgVtb7Y2Fg=="], + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + "handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="], + + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hash-base": ["hash-base@2.0.2", "", { "dependencies": { "inherits": "^2.0.1" } }, "sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw=="], + + "hash.js": ["hash.js@1.1.7", "", { "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" } }, "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA=="], + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "hmac-drbg": ["hmac-drbg@1.0.1", "", { "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg=="], + + "hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], + "http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="], "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "index-to-position": ["index-to-position@1.1.0", "", {}, "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg=="], + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], + + "is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="], + "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], + + "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], + "isows": ["isows@1.0.6", "", { "peerDependencies": { "ws": "*" } }, "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "long": ["long@4.0.0", "", {}, "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="], + + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], + "meow": ["meow@13.2.0", "", {}, "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA=="], + "merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="], "methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="], @@ -165,10 +293,20 @@ "mime-types": ["mime-types@3.0.0", "", { "dependencies": { "mime-db": "^1.53.0" } }, "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w=="], + "minimalistic-assert": ["minimalistic-assert@1.0.1", "", {}, "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="], + + "minimalistic-crypto-utils": ["minimalistic-crypto-utils@1.0.1", "", {}, "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + "ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], + + "normalize-package-data": ["normalize-package-data@6.0.2", "", { "dependencies": { "hosted-git-info": "^7.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" } }, "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g=="], + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], @@ -179,12 +317,20 @@ "ox": ["ox@0.6.9", "", { "dependencies": { "@adraffy/ens-normalize": "^1.10.1", "@noble/curves": "^1.6.0", "@noble/hashes": "^1.5.0", "@scure/bip32": "^1.5.0", "@scure/bip39": "^1.4.0", "abitype": "^1.0.6", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug=="], + "parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="], + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], "path-to-regexp": ["path-to-regexp@0.1.12", "", {}, "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="], + "pbkdf2": ["pbkdf2@3.1.3", "", { "dependencies": { "create-hash": "~1.1.3", "create-hmac": "^1.1.7", "ripemd160": "=2.0.1", "safe-buffer": "^5.2.1", "sha.js": "^2.4.11", "to-buffer": "^1.2.0" } }, "sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + "pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="], + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], "qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="], @@ -193,18 +339,34 @@ "raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="], + "read-package-up": ["read-package-up@11.0.0", "", { "dependencies": { "find-up-simple": "^1.0.0", "read-pkg": "^9.0.0", "type-fest": "^4.6.0" } }, "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ=="], + + "read-pkg": ["read-pkg@9.0.1", "", { "dependencies": { "@types/normalize-package-data": "^2.4.3", "normalize-package-data": "^6.0.0", "parse-json": "^8.0.0", "type-fest": "^4.6.0", "unicorn-magic": "^0.1.0" } }, "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA=="], + + "ripemd160": ["ripemd160@2.0.1", "", { "dependencies": { "hash-base": "^2.0.0", "inherits": "^2.0.1" } }, "sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w=="], + "router": ["router@2.1.0", "", { "dependencies": { "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA=="], "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "scrypt-js": ["scrypt-js@3.0.1", "", {}, "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA=="], + + "scryptsy": ["scryptsy@2.1.0", "", {}, "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w=="], + + "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + "send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="], "serve-static": ["serve-static@1.16.2", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.19.0" } }, "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw=="], + "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], + "sha.js": ["sha.js@2.4.12", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.0" }, "bin": { "sha.js": "bin.js" } }, "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w=="], + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], @@ -213,24 +375,58 @@ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "spdx-correct": ["spdx-correct@3.2.0", "", { "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA=="], + + "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], + + "spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], + + "spdx-license-ids": ["spdx-license-ids@3.0.22", "", {}, "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ=="], + "statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], + "temp-dir": ["temp-dir@3.0.0", "", {}, "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw=="], + + "tempfile": ["tempfile@5.0.0", "", { "dependencies": { "temp-dir": "^3.0.0" } }, "sha512-bX655WZI/F7EoTDw9JvQURqAXiPHi8o8+yFxPF2lWYyz1aHnmMRuXWqL6YB6GmeO0o4DIYWHLgGNi/X64T+X4Q=="], + + "to-buffer": ["to-buffer@1.2.1", "", { "dependencies": { "isarray": "^2.0.5", "safe-buffer": "^5.2.1", "typed-array-buffer": "^1.0.3" } }, "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ=="], + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + "tslib": ["tslib@2.3.1", "", {}, "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="], + + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], + "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], + "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], + "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="], + "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + "unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], + "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + + "validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="], + "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], "viem": ["viem@2.23.9", "", { "dependencies": { "@noble/curves": "1.8.1", "@noble/hashes": "1.7.1", "@scure/bip32": "1.6.2", "@scure/bip39": "1.5.4", "abitype": "1.0.8", "isows": "1.0.6", "ox": "0.6.9", "ws": "8.18.1" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-y8VLPfKukrstZKTerS9bm45ajZ22wUyStF+VquK3I2OovWLOyXSbQmJWei8syMFhp1uwhxh1tb0fAdx0WSRZWg=="], + "which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="], + + "wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="], + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], "ws": ["ws@8.18.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w=="], diff --git a/cd/base/configmap.yaml b/cd/base/configmap.yaml new file mode 100644 index 0000000..1510bc3 --- /dev/null +++ b/cd/base/configmap.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: evm-mcp-server + namespace: evm-mcp-server + labels: + app.kubernetes.io/name: evm-mcp-server +data: + NODE_ENV: "production" + PORT: "3000" + HOST: "0.0.0.0" \ No newline at end of file diff --git a/cd/base/deployment.yaml b/cd/base/deployment.yaml new file mode 100644 index 0000000..f13a778 --- /dev/null +++ b/cd/base/deployment.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: evm-mcp-server + namespace: evm-mcp-server + labels: + app.kubernetes.io/name: evm-mcp-server +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: evm-mcp-server + template: + metadata: + labels: + app.kubernetes.io/name: evm-mcp-server + spec: + containers: + - name: evm-mcp-server + image: evm-mcp-server + ports: + - containerPort: 3000 + name: http + protocol: TCP + envFrom: + - configMapRef: + name: evm-mcp-server + resources: + requests: + memory: "128Mi" + limits: + memory: "512Mi" diff --git a/cd/base/healthcheckpolicy.yaml b/cd/base/healthcheckpolicy.yaml new file mode 100644 index 0000000..5881739 --- /dev/null +++ b/cd/base/healthcheckpolicy.yaml @@ -0,0 +1,23 @@ +apiVersion: networking.gke.io/v1 +kind: HealthCheckPolicy +metadata: + name: evm-mcp-server + namespace: evm-mcp-server + labels: + app.kubernetes.io/name: evm-mcp-server +spec: + default: + checkIntervalSec: 10 + timeoutSec: 5 + healthyThreshold: 1 + unhealthyThreshold: 3 + config: + type: HTTP + httpHealthCheck: + portSpecification: USE_FIXED_PORT + requestPath: /health + port: 3000 + targetRef: + group: "" + kind: Service + name: evm-mcp-server diff --git a/cd/base/httproute.yaml b/cd/base/httproute.yaml new file mode 100644 index 0000000..503de1f --- /dev/null +++ b/cd/base/httproute.yaml @@ -0,0 +1,18 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: evm-mcp-server + namespace: evm-mcp-server + labels: + app.kubernetes.io/name: evm-mcp-server +spec: + parentRefs: + - name: mcp-gateway + namespace: mcp-gateway-stg + sectionName: https + hostnames: + - "evm.mcp.zilliqa.com" + rules: + - backendRefs: + - name: evm-mcp-server + port: 3000 \ No newline at end of file diff --git a/cd/base/kustomization.yaml b/cd/base/kustomization.yaml new file mode 100644 index 0000000..d568ca3 --- /dev/null +++ b/cd/base/kustomization.yaml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - namespace.yaml + - configmap.yaml + - deployment.yaml + - service.yaml + - httproute.yaml + - healthcheckpolicy.yaml \ No newline at end of file diff --git a/cd/base/namespace.yaml b/cd/base/namespace.yaml new file mode 100644 index 0000000..d808f58 --- /dev/null +++ b/cd/base/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: evm-mcp-server diff --git a/cd/base/service.yaml b/cd/base/service.yaml new file mode 100644 index 0000000..5a140b3 --- /dev/null +++ b/cd/base/service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: evm-mcp-server + namespace: evm-mcp-server + labels: + app.kubernetes.io/name: evm-mcp-server +spec: + type: ClusterIP + ports: + - port: 3000 + targetPort: 3000 + protocol: TCP + name: http + appProtocol: HTTP + selector: + app.kubernetes.io/name: evm-mcp-server diff --git a/cd/overlays/production/kustomization.yaml b/cd/overlays/production/kustomization.yaml new file mode 100644 index 0000000..065976a --- /dev/null +++ b/cd/overlays/production/kustomization.yaml @@ -0,0 +1,22 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../base + +namespace: evm-mcp-server-prd + +patches: + - target: + kind: HTTPRoute + name: evm-mcp-server + patch: |- + - op: replace + path: "/spec/hostnames/0" + value: evm.mcp.zilliqa.com + - op: replace + path: "/spec/parentRefs/0/name" + value: mcp-gateway + - op: replace + path: "/spec/parentRefs/0/namespace" + value: mcp-gateway-prd diff --git a/cd/overlays/staging/kustomization.yaml b/cd/overlays/staging/kustomization.yaml new file mode 100644 index 0000000..bf5a0bc --- /dev/null +++ b/cd/overlays/staging/kustomization.yaml @@ -0,0 +1,22 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../base + +namespace: evm-mcp-server-stg + +patches: + - target: + kind: HTTPRoute + name: evm-mcp-server + patch: |- + - op: replace + path: "/spec/hostnames/0" + value: evm.mcp.zilstg.dev + - op: replace + path: "/spec/parentRefs/0/name" + value: mcp-gateway + - op: replace + path: "/spec/parentRefs/0/namespace" + value: mcp-gateway-stg diff --git a/package.json b/package.json index ab33609..c2a2143 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "typescript": "^5.8.2" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.17.4", + "@zilliqa-js/crypto": "^3.5.0", "cors": "^2.8.5", "express": "^4.21.2", "viem": "^2.23.9", diff --git a/src/core/chains.ts b/src/core/chains.ts index c5c5dda..f2d8c19 100644 --- a/src/core/chains.ts +++ b/src/core/chains.ts @@ -32,6 +32,7 @@ import { aurora, canto, flowMainnet, + zilliqa, // Testnets sepolia, @@ -57,7 +58,8 @@ import { goerli, holesky, flowTestnet, - filecoinCalibration + filecoinCalibration, + zilliqaTestnet, } from 'viem/chains'; // Default configuration values @@ -98,6 +100,7 @@ export const chainMap: Record = { 1313161554: aurora, 7700: canto, 747: flowMainnet, + 32769: zilliqa, // Zilliqa mainnet // Testnets 11155111: sepolia, @@ -124,6 +127,7 @@ export const chainMap: Record = { 17000: holesky, 545: flowTestnet, 314159: filecoinCalibration, + 33101: zilliqaTestnet, // Zilliqa testnet }; // Map network names to chain IDs for easier reference @@ -172,6 +176,8 @@ export const networkNameMap: Record = { 'aurora': 1313161554, 'canto': 7700, 'flow': 747, + 'zilliqa': 32769, + 'zil': 32769, // Testnets 'sepolia': 11155111, @@ -218,6 +224,8 @@ export const networkNameMap: Record = { 'holesky': 17000, 'flow-testnet': 545, 'filecoin-calibration': 314159, + 'zilliqa-testnet': 33101, + 'zilliqatestnet': 33101, }; // Map chain IDs to RPC URLs @@ -254,6 +262,7 @@ export const rpcUrlMap: Record = { 1313161554: 'https://mainnet.aurora.dev', 7700: 'https://canto.gravitychain.io', 747: 'https://mainnet.evm.nodes.onflow.org', + 32769: 'https://api.zilliqa.com', // Testnets 11155111: 'https://sepolia.drpc.org', @@ -280,6 +289,7 @@ export const rpcUrlMap: Record = { 17000: 'https://ethereum-holesky.publicnode.com', 545: 'https://testnet.evm.nodes.onflow.org', 314159: 'https://api.calibration.node.glif.io/rpc/v1', + 33101: 'https://api.testnet.zilliqa.com', }; /** diff --git a/src/core/services/clients.ts b/src/core/services/clients.ts index c74c125..0902016 100644 --- a/src/core/services/clients.ts +++ b/src/core/services/clients.ts @@ -16,7 +16,7 @@ const clientCache = new Map(); /** * Get a public client for a specific network */ -export function getPublicClient(network = 'ethereum'): PublicClient { +export function getPublicClient(network: number | string = 'ethereum'): PublicClient { const cacheKey = String(network); // Return cached client if available diff --git a/src/core/services/index.ts b/src/core/services/index.ts index f7da50a..4ff7662 100644 --- a/src/core/services/index.ts +++ b/src/core/services/index.ts @@ -7,6 +7,7 @@ export * from './transactions.js'; export * from './contracts.js'; export * from './tokens.js'; export * from './ens.js'; +export * as zilliqa from './zilliqa.js'; export { utils as helpers } from './utils.js'; // Re-export common types for convenience diff --git a/src/core/services/zilliqa.ts b/src/core/services/zilliqa.ts new file mode 100644 index 0000000..0de9a93 --- /dev/null +++ b/src/core/services/zilliqa.ts @@ -0,0 +1,26 @@ +import { fromBech32Address, toBech32Address } from '@zilliqa-js/crypto'; +import { validation } from '@zilliqa-js/util'; + +/** + * Converts a Zilliqa bech32 address to a hex address. + * + * @param bech32Address The Zilliqa bech32 address (e.g., 'zil1...') + * @returns The hex address (e.g., '0x...') + */ +export function zilToHex(bech32Address: string): string { + // fromBech32Address will throw if invalid + return fromBech32Address(bech32Address); +} + +/** + * Converts a hex address to a Zilliqa bech32 address. + * + * @param hexAddress The hex address (e.g., '0x...') + * @returns The Zilliqa bech32 address (e.g., 'zil1...') + */ +export function hexToZil(hexAddress: string): string { + if (!validation.isAddress(hexAddress)) { + throw new Error('Invalid hex address'); + } + return toBech32Address(hexAddress); +} diff --git a/src/core/tools.ts b/src/core/tools.ts index a01d7a7..6dced15 100644 --- a/src/core/tools.ts +++ b/src/core/tools.ts @@ -1193,4 +1193,219 @@ export function registerEVMTools(server: McpServer) { } } ); + + // ZILLIQA TOOLS + + // Convert Zilliqa address between bech32 (zil1...) and hex (0x...) formats + server.tool( + "convert_zilliqa_address", + "Convert a Zilliqa address between bech32 (zil1...) and hex (0x...) formats. Only valid for Zilliqa networks.", + { + address: z.string().describe("The Zilliqa address to convert (either 'zil1...' or '0x...')"), + direction: z.enum(["bech32-to-hex", "hex-to-bech32"]).optional().describe("Direction of conversion. If omitted, inferred from the input."), + network: z.string().optional().describe("Network must be 'zilliqa' or 'zilliqa-testnet'. Defaults to 'zilliqa'.") + }, + async ({ address, direction, network = "zilliqa" }) => { + try { + const chainId = await services.getChainId(network); + const isZilliqaNetwork = chainId === 32769 || chainId === 33101; + if (!isZilliqaNetwork) { + return { + content: [{ + type: "text", + text: `Error: Network '${network}' (chainId ${chainId}) is not a Zilliqa network. Use 'zilliqa' or 'zilliqa-testnet'.` + }], + isError: true + }; + } + + let inferredDirection: "bech32-to-hex" | "hex-to-bech32" | undefined = direction; + if (!inferredDirection) { + if (address.startsWith("zil1")) inferredDirection = "bech32-to-hex"; + else if (address.startsWith("0x") || /^[0-9a-fA-F]{40}$/.test(address)) inferredDirection = "hex-to-bech32"; + } + + if (!inferredDirection) { + return { + content: [{ + type: "text", + text: `Error: Unable to infer direction. Provide a 'zil1...' bech32 or a '0x...' hex address, or set the 'direction' explicitly.` + }], + isError: true + }; + } + + let output: string; + if (inferredDirection === "bech32-to-hex") { + output = services.zilliqa.zilToHex(address); + } else { + const hexInput = address.startsWith("0x") ? address : `0x${address}`; + output = services.zilliqa.hexToZil(hexInput); + } + + return { + content: [{ + type: "text", + text: JSON.stringify({ + network, + chainId, + input: address, + output, + direction: inferredDirection + }, null, 2) + }] + }; + } catch (error) { + return { + content: [{ + type: "text", + text: `Error converting Zilliqa address: ${error instanceof Error ? error.message : String(error)}` + }], + isError: true + }; + } + } + ); + + // Request Zilliqa faucet tokens + server.tool( + "request_zilliqa_faucet", + "Request test tokens from Zilliqa testnet faucet for development and testing purposes", + { + address: z.string().describe("Zilliqa address to receive tokens (can be bech32 'zil1...' or hex '0x...' format)") + }, + async ({ address }) => { + try { + // Validate the address first + let isValidAddress = false; + let validationError = ''; + + try { + if (address.startsWith('zil1')) { + // Validate bech32 address + services.zilliqa.zilToHex(address); + isValidAddress = true; + } else if (address.startsWith('0x') || /^[0-9a-fA-F]{40}$/.test(address)) { + // Validate hex address + services.zilliqa.hexToZil(address.startsWith('0x') ? address : `0x${address}`); + isValidAddress = true; + } else { + validationError = 'Address must be in bech32 (zil1...) or hex (0x...) format'; + } + } catch (error) { + validationError = error instanceof Error ? error.message : 'Invalid Zilliqa address format'; + } + + if (!isValidAddress) { + return { + content: [{ + type: "text", + text: `**Faucet Request Failed**\n\n❌ Invalid address: ${validationError}` + }], + isError: true + }; + } + + // Use testnet faucet URL + const faucetUrl = "https://faucet.zq2-testnet.zilliqa.com"; + + try { + // Make request to faucet + const response = await fetch(faucetUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: `address=${encodeURIComponent(address)}`, + }); + + const responseText = await response.text(); + + if (response.ok) { + // Check if the response indicates success + if (responseText.includes('notification is-success') || responseText.includes('Request sent') || responseText.includes('Transaction ID')) { + // Extract transaction ID if available + const txMatch = responseText.match(/Transaction ID:.*?href="[^"]*\/tx\/([^"]+)"/); + const txId = txMatch ? txMatch[1] : null; + + return { + content: [{ + type: "text", + text: `**Faucet Request Successful** ✅\n\n` + + `**Network:** testnet\n` + + `**Address:** \`${address}\`\n` + + `**Amount:** 100 ZIL\n` + + `**Status:** Request submitted successfully\n` + + `${txId ? `**Transaction ID:** \`${txId}\`\n` : ''}` + + `**Explorer:** https://otterscan.testnet.zilliqa.com${txId ? `/tx/${txId}` : ''}\n\n` + + `**Note:** It may take a few moments for the tokens to appear in your account.` + }] + }; + } + // Check if the response indicates an error (rate limiting, etc.) + else if (responseText.includes('notification is-danger') || responseText.includes('Request made too recently') || responseText.includes('error')) { + // Extract error message if available + const errorMatch = responseText.match(/notification is-danger[^>]*>.*?]*>[^<]*<\/button>\s*([^<]+)/); + const errorMessage = errorMatch ? errorMatch[1].trim() : 'Faucet request was not successful'; + + return { + content: [{ + type: "text", + text: `**Faucet Request Failed** ❌\n\n` + + `**Network:** testnet\n` + + `**Address:** \`${address}\`\n` + + `**Error:** ${errorMessage}\n\n` + + `**Suggestion:** ${errorMessage.includes('too recently') ? 'Please wait and try again later.' : 'Try again later or contact Zilliqa support if the issue persists.'}` + }], + isError: true + }; + } else { + return { + content: [{ + type: "text", + text: `**Faucet Request Status Unknown** ⚠️\n\n` + + `**Network:** testnet\n` + + `**Address:** \`${address}\`\n` + + `**Status:** Request submitted but status unclear\n\n` + + `**Note:** Please check your balance on the testnet explorer to verify if tokens were received.` + }] + }; + } + } else { + return { + content: [{ + type: "text", + text: `**Faucet Request Failed** ❌\n\n` + + `**Network:** testnet\n` + + `**Address:** \`${address}\`\n` + + `**Error:** HTTP ${response.status} - ${response.statusText}\n\n` + + `**Details:** The faucet service returned an error. This could be due to rate limiting or temporary service issues.` + }], + isError: true + }; + } + } catch (error) { + return { + content: [{ + type: "text", + text: `**Faucet Request Failed** ❌\n\n` + + `**Network:** testnet\n` + + `**Address:** \`${address}\`\n` + + `**Error:** ${error instanceof Error ? error.message : String(error)}\n\n` + + `**Suggestion:** Check your internet connection and try again.` + }], + isError: true + }; + } + } catch (error) { + return { + content: [{ + type: "text", + text: `Error processing faucet request: ${error instanceof Error ? error.message : String(error)}` + }], + isError: true + }; + } + } + ); } \ No newline at end of file diff --git a/src/server/http-server.ts b/src/server/http-server.ts index 216672c..aa5489a 100644 --- a/src/server/http-server.ts +++ b/src/server/http-server.ts @@ -1,15 +1,15 @@ import { config } from "dotenv"; -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import startServer from "./server.js"; import express, { Request, Response } from "express"; import cors from "cors"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -// Environment variables - hardcoded values -const PORT = 3001; +// Environment variables +const PORT = 3000; const HOST = '0.0.0.0'; -console.error(`Configured to listen on ${HOST}:${PORT}`); +console.error(`Starting EVM MCP Server on ${HOST}:${PORT}`); // Setup Express const app = express(); @@ -25,9 +25,6 @@ app.use(cors({ // Add OPTIONS handling for preflight requests app.options('*', cors()); -// Keep track of active connections with session IDs -const connections = new Map(); - // Initialize the server let server: McpServer | null = null; startServer().then(s => { @@ -38,165 +35,117 @@ startServer().then(s => { process.exit(1); }); -// Define routes +// Health check endpoint +app.get("/health", (req: Request, res: Response) => { + res.json({ + status: "ok", + server: server ? "initialized" : "not initialized" + }); +}); + +// Endpoint for StreamableHTTP connection // @ts-ignore -app.get("/sse", (req: Request, res: Response) => { - console.error(`Received SSE connection request from ${req.ip}`); - console.error(`Query parameters: ${JSON.stringify(req.query)}`); - - // Set CORS headers explicitly - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); +app.get('/mcp', (req: Request, res: Response) => { + console.error(`Received connection request from ${req.ip}`); if (!server) { - console.error("Server not initialized yet, rejecting SSE connection"); - return res.status(503).send("Server not initialized"); + console.error("Server not initialized yet"); + return res.status(503).json({ error: "Server not initialized" }); } - // Generate a unique session ID if one is not provided - // The sessionId is crucial for mapping SSE connections to message handlers - const sessionId = generateSessionId(); - console.error(`Creating SSE session with ID: ${sessionId}`); + // Set headers + res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }); - // Set SSE headers - res.setHeader("Content-Type", "text/event-stream"); - res.setHeader("Cache-Control", "no-cache, no-transform"); - res.setHeader("Connection", "keep-alive"); + // Keep connection alive without sending initial message + // Gemini will handle the connection establishment through POST requests - // Create transport - handle before writing to response - try { - console.error(`Creating SSE transport for session: ${sessionId}`); - - // Create and store the transport keyed by session ID - // Note: The path must match what the client expects (typically "/messages") - const transport = new SSEServerTransport("/messages", res); - connections.set(sessionId, transport); - - // Handle connection close - req.on("close", () => { - console.error(`SSE connection closed for session: ${sessionId}`); - connections.delete(sessionId); - }); - - // Connect transport to server - this must happen before sending any data - server.connect(transport).then(() => { - // Send an initial event with the session ID for the client to use in messages - // Only send this after the connection is established - console.error(`SSE connection established for session: ${sessionId}`); - - // Send the session ID to the client - res.write(`data: ${JSON.stringify({ type: "session_init", sessionId })}\n\n`); - }).catch((error: Error) => { - console.error(`Error connecting transport to server: ${error}`); - connections.delete(sessionId); - }); - } catch (error) { - console.error(`Error creating SSE transport: ${error}`); - connections.delete(sessionId); - res.status(500).send(`Internal server error: ${error}`); - } + // Handle client disconnect + req.on('close', () => { + console.error('Connection closed'); + }); }); +// Main MCP endpoint - stateless mode // @ts-ignore -app.post("/messages", (req: Request, res: Response) => { - // Extract the session ID from the URL query parameters - let sessionId = req.query.sessionId?.toString(); - - // If no sessionId is provided and there's only one connection, use that - if (!sessionId && connections.size === 1) { - sessionId = Array.from(connections.keys())[0]; - console.error(`No sessionId provided, using the only active session: ${sessionId}`); - } - - console.error(`Received message for sessionId ${sessionId}`); - console.error(`Message body: ${JSON.stringify(req.body)}`); - - // Set CORS headers - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS'); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); +app.post('/mcp', async (req: Request, res: Response) => { + console.error(`Received MCP request from ${req.ip}`); if (!server) { console.error("Server not initialized yet"); return res.status(503).json({ error: "Server not initialized" }); } - if (!sessionId) { - console.error("No session ID provided and multiple connections exist"); - return res.status(400).json({ - error: "No session ID provided. Please provide a sessionId query parameter or connect to /sse first.", - activeConnections: connections.size - }); - } - - const transport = connections.get(sessionId); - if (!transport) { - console.error(`Session not found: ${sessionId}`); - return res.status(404).json({ error: "Session not found" }); - } - - console.error(`Handling message for session: ${sessionId}`); try { - transport.handlePostMessage(req, res).catch((error: Error) => { - console.error(`Error handling post message: ${error}`); - res.status(500).json({ error: `Internal server error: ${error.message}` }); + // Create a new transport for each request (stateless mode) + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, // Stateless mode + enableJsonResponse: true, // Return JSON instead of SSE }); + + // Handle request close + res.on('close', () => { + console.error('Request closed'); + transport.close(); + }); + + // Connect transport to server + await server.connect(transport); + + // Handle the request + await transport.handleRequest(req, res, req.body); + } catch (error) { - console.error(`Exception handling post message: ${error}`); - res.status(500).json({ error: `Internal server error: ${error}` }); + console.error('Error handling MCP request:', error); + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: '2.0', + error: { + code: -32603, + message: 'Internal server error', + data: error instanceof Error ? error.message : String(error) + }, + id: (req.body as any)?.id || null, + }); + } } }); -// Add a simple health check endpoint -app.get("/health", (req: Request, res: Response) => { - res.status(200).json({ - status: "ok", - server: server ? "initialized" : "initializing", - activeConnections: connections.size, - connectedSessionIds: Array.from(connections.keys()) - }); -}); - -// Add a root endpoint for basic info -app.get("/", (req: Request, res: Response) => { - res.status(200).json({ - name: "MCP Server", - version: "1.0.0", - endpoints: { - sse: "/sse", - messages: "/messages", - health: "/health" - }, - status: server ? "ready" : "initializing", - activeConnections: connections.size - }); +// Start the server +const httpServer = app.listen(PORT, HOST, (error) => { + if (error) { + console.error('Failed to start server:', error); + process.exit(1); + } + console.error(`EVM MCP Server listening on port ${PORT}`); + console.error(`Endpoint: http://${HOST}:${PORT}/mcp`); + console.error(`Health: http://${HOST}:${PORT}/health`); }); -// Helper function to generate a UUID-like session ID -function generateSessionId(): string { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { - const r = Math.random() * 16 | 0; - const v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); -} - -// Handle process termination gracefully +// Handle graceful shutdown process.on('SIGINT', () => { - console.error('Shutting down server...'); - connections.forEach((transport, sessionId) => { - console.error(`Closing connection for session: ${sessionId}`); + console.error('\nReceived SIGINT, shutting down gracefully...'); + if (server) { + server.close(); + } + httpServer.close(() => { + console.error('HTTP server closed'); + process.exit(0); }); - process.exit(0); }); -// Start the HTTP server on a different port (3001) to avoid conflicts -const httpServer = app.listen(PORT, HOST, () => { - console.error(`Template MCP Server running at http://${HOST}:${PORT}`); - console.error(`SSE endpoint: http://${HOST}:${PORT}/sse`); - console.error(`Messages endpoint: http://${HOST}:${PORT}/messages (sessionId optional if only one connection)`); - console.error(`Health check: http://${HOST}:${PORT}/health`); -}).on('error', (err: Error) => { - console.error(`Server error: ${err}`); +process.on('SIGTERM', () => { + console.error('\nReceived SIGTERM, shutting down gracefully...'); + if (server) { + server.close(); + } + httpServer.close(() => { + console.error('HTTP server closed'); + process.exit(0); + }); }); \ No newline at end of file