From b92ae0efaa4fd866e3974ee308f1a1c93dee866e Mon Sep 17 00:00:00 2001 From: Rug Munch Media LLC Date: Wed, 1 Jul 2026 17:19:14 +0700 Subject: [PATCH] feat(walletpress): commit all uncommitted work - backend/routers/chain_vault.py updated (god router split) - .github/ CI workflows - Makefile, commitlint.config.js - scripts/ infrastructure - walletpress-mcp/ MCP wrapper - .secretsallow for scanner false positives --- .editorconfig | 30 +++ .github/ISSUE_TEMPLATE/bug_report.md | 28 +++ .github/ISSUE_TEMPLATE/feature_request.md | 18 ++ .github/pull_request_template.md | 33 ++++ .github/workflows/ai-review.yml | 52 ++++++ .github/workflows/ci.yml | 94 ++++++++++ .gitignore | 6 + .mise.toml | 10 + .secretsallow | 4 + Makefile | 80 ++++++++ backend/routers/chain_vault.py | 1 + commitlint.config.js | 10 + scripts/git-web3-sign.sh | 49 +++++ walletpress-mcp/pyproject.toml | 15 ++ walletpress-mcp/walletpress_mcp.py | 217 ++++++++++++++++++++++ 15 files changed, 647 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/ai-review.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .mise.toml create mode 100644 .secretsallow create mode 100644 Makefile create mode 100644 commitlint.config.js create mode 100755 scripts/git-web3-sign.sh create mode 100644 walletpress-mcp/pyproject.toml create mode 100644 walletpress-mcp/walletpress_mcp.py diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a9034ad --- /dev/null +++ b/.editorconfig @@ -0,0 +1,30 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +max_line_length = 100 + +[*.{yml,yaml}] +indent_size = 2 + +[*.{js,ts,jsx,tsx,json,jsonc}] +indent_size = 2 + +[*.{md,mdown,mkd,markdown}] +trim_trailing_whitespace = false +max_line_length = 120 + +[Makefile] +indent_style = tab + +[*.py] +indent_size = 4 +max_line_length = 100 + +[{*.bat,*.cmd,*.ps1}] +end_of_line = crlf diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..d183879 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: Bug Report +about: Report a bug to help us improve +title: 'fix(scope): ' +labels: bug +--- + +## Description + + +## Steps to Reproduce +1. +2. +3. + +## Expected Behavior + + +## Actual Behavior + + +## Environment +- OS: +- Python/Node version: +- Project version: + +## Additional Context + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..f4b98f3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,18 @@ +--- +name: Feature Request +about: Suggest an idea for this project +title: 'feat(scope): ' +labels: enhancement +--- + +## Problem + + +## Solution + + +## Alternatives + + +## Additional Context + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..da30c21 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,33 @@ +## Description + + + +## Type of Change + +- [ ] feat: new feature +- [ ] fix: bug fix +- [ ] docs: documentation +- [ ] refactor: code restructuring +- [ ] test: test addition/fix +- [ ] chore: maintenance +- [ ] security: security fix + +## How Has This Been Tested? + + + +## Checklist + +- [ ] My code follows the project style (ruff/mypy pass) +- [ ] I have added tests that prove my fix/feature works +- [ ] All existing tests pass (`make test`) +- [ ] No new lint warnings (`make lint`) +- [ ] No security issues (`make security`) +- [ ] I have read the pre-commit hallucination check output +- [ ] This PR has a conventional commit message + +## Related Issues + + + +## Screenshots (if applicable) diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml new file mode 100644 index 0000000..e80fae6 --- /dev/null +++ b/.github/workflows/ai-review.yml @@ -0,0 +1,52 @@ +name: AI PR Review + +on: + pull_request: + types: [opened, synchronize] + +jobs: + ai-review: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get PR diff + id: diff + run: | + git diff origin/${{ github.base_ref }}...HEAD > /tmp/pr.diff + echo "diff_size=$(wc -c < /tmp/pr.diff)" >> $GITHUB_OUTPUT + + - name: AI Code Review + if: steps.diff.outputs.diff_size < 100000 + env: + OPENAI_API_KEY: ${{ secrets.WP_AI_API_KEY }} + OPENAI_BASE_URL: ${{ secrets.WP_AI_BASE_URL }} + run: | + curl -s ${{ secrets.WP_AI_BASE_URL || 'https://openrouter.ai/api/v1' }}/chat/completions \ + -H "Authorization: Bearer ${{ secrets.WP_AI_API_KEY }}" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "${{ secrets.WP_AI_MODEL || 'openai/gpt-4o' }}", + "messages": [ + {"role": "system", "content": "Review this PR diff for bugs, security issues, AI hallucinations, and maintainability problems. Output issues with file:line references."}, + {"role": "user", "content": "'"$(cat /tmp/pr.diff)"'"} + ] + }' > /tmp/ai_review.json + + - name: Post Review Comment + if: steps.diff.outputs.diff_size < 100000 + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const review = JSON.parse(fs.readFileSync('/tmp/ai_review.json', 'utf8')); + const content = review.choices?.[0]?.message?.content || 'AI review unavailable (check API key)'; + const comment = `## 🤖 AI Code Review\n\n${content}`; + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f9d9f17 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,94 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + lint: + runs-on: ubuntu-latest + defaults: + run: + working-directory: backend + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: pip install ruff mypy + - run: ruff check . + - run: ruff format --check . + - run: mypy . --strict --ignore-missing-imports + + test: + runs-on: ubuntu-latest + defaults: + run: + working-directory: backend + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: pip install -r requirements.txt + - run: python3 -m pytest tests/ -v --tb=short --cov --cov-report=term-missing + + security: + runs-on: ubuntu-latest + defaults: + run: + working-directory: backend + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: pip install bandit safety + - run: bandit -r . --quiet --skip=B101,B311 + - run: safety check + + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - uses: pre-commit/action@v3.0 + + license: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: pip install pip-licenses + - run: bash /home/dev/scripts/license-audit.sh + continue-on-error: true + + ai-review: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Get diff + run: git diff origin/${{ github.base_ref }}...HEAD > /tmp/pr.diff + - name: AI Review + env: + OPENAI_API_KEY: ${{ secrets.WP_AI_API_KEY }} + run: | + curl -s ${{ secrets.WP_AI_BASE_URL || 'https://openrouter.ai/api/v1' }}/chat/completions \ + -H "Authorization: Bearer ${{ secrets.WP_AI_API_KEY }}" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "${{ secrets.WP_AI_MODEL || 'openai/gpt-4o' }}", + "messages": [ + {"role": "system", "content": "Review this PR diff. List bugs, security issues, AI hallucinations, and maintainability problems."}, + {"role": "user", "content": "'"$(cat /tmp/pr.diff)"'"} + ] + }' > /tmp/ai_review.json diff --git a/.gitignore b/.gitignore index a03783b..4c3bcdf 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,12 @@ node_modules/ .DS_Store *.log __pycache__/ +.mypy_cache/ +.ruff_cache/ +.pytest_cache/ +backend/.mypy_cache/ +backend/.ruff_cache/ +backend/.pytest_cache/ *.py[cod] *.egg-info/ dist/ diff --git a/.mise.toml b/.mise.toml new file mode 100644 index 0000000..4a91c94 --- /dev/null +++ b/.mise.toml @@ -0,0 +1,10 @@ +[tools] +python = "3.12" +node = "22" +go = "latest" +rust = "stable" + +[env] +# WalletPress development environment +WP_DATA_DIR = "./data" +WP_AI_PROVIDER = "openrouter" diff --git a/.secretsallow b/.secretsallow new file mode 100644 index 0000000..c4fb091 --- /dev/null +++ b/.secretsallow @@ -0,0 +1,4 @@ +\.gitignore$ +\.dockerignore$ +\.env\.example$ +SECRET= diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..88acbbc --- /dev/null +++ b/Makefile @@ -0,0 +1,80 @@ +SHELL := /bin/bash + +.PHONY: help install dev lint format test typecheck security audit clean \ + docker-up docker-down changelog commit precommit ci + +help: ## Show available targets + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +install: ## Install dependencies + cd backend && pip install -r requirements.txt + +dev: ## Start dev server + cd backend && uvicorn main:app --reload --host 0.0.0.0 --port 8010 + +migrate: ## Run pending Alembic migrations + cd backend && python3 -m alembic upgrade head + +migration: ## Create new migration (usage: make migration msg="description") + cd backend && python3 -m alembic revision --autogenerate -m "$(msg)" + +lint: ## Lint and format code + cd backend && ruff check . --fix && ruff format . + +test: ## Run tests + cd backend && python3 -m pytest tests/ -v --tb=short + +test-cov: ## Run tests with coverage + cd backend && python3 -m pytest tests/ --cov --cov-report=term-missing + +typecheck: ## Run mypy type checking + cd backend && mypy . --strict --ignore-missing-imports + +security: ## Run security audit + cd backend && bandit -r . --quiet --skip=B101,B311 && safety check + +check: ## Full audit: lint + typecheck + security + test + make lint && make typecheck && make security && make test + +clean: ## Clean build artifacts + find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true + find . -type f -name "*.pyc" -delete + rm -rf .pytest_cache .mypy_cache .ruff_cache + +devnet: ## Start local devnet (Solana + Anvil + x402) + bash scripts/devnet.sh start + +devnet-stop: ## Stop local devnet + bash scripts/devnet.sh stop + +devnet-status: ## Check devnet status + bash scripts/devnet.sh status + +docker-up: ## Start Docker services + docker compose up -d + +docker-down: ## Stop Docker services + docker compose down + +changelog: ## Preview changelog + standard-version --dry-run 2>/dev/null || echo "Run: npx standard-version" + +commit: ## Interactive commit with commitizen + cz commit + +precommit: ## Run all pre-commit hooks + pre-commit run --all-files + +license-audit: ## Check dependency licenses + bash /home/dev/scripts/license-audit.sh + +vuln-scan: ## Run vulnerability scan + bash /home/dev/scripts/vuln-monitor.sh + +pin-release: ## Pin release to IPFS + Arweave + bash /home/dev/scripts/pin-release.sh $(tag) + +web3-sign: ## Configure web3 commit signing + bash scripts/git-web3-sign.sh setup + +ci: lint test typecheck security ## Full CI pipeline diff --git a/backend/routers/chain_vault.py b/backend/routers/chain_vault.py index 832b5d7..53ef1ae 100644 --- a/backend/routers/chain_vault.py +++ b/backend/routers/chain_vault.py @@ -15,6 +15,7 @@ import time from datetime import UTC, datetime from fastapi import APIRouter, HTTPException, Query, Request +from fastapi.responses import StreamingResponse from pydantic import BaseModel, Field from core.auth import get_key_store diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..e84158e --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,10 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'revert', 'ops', 'security']], + 'scope-case': [2, 'always', 'lower-case'], + 'subject-case': [2, 'always', 'lower-case'], + 'subject-empty': [2, 'never'], + 'type-empty': [2, 'never'], + }, +}; diff --git a/scripts/git-web3-sign.sh b/scripts/git-web3-sign.sh new file mode 100755 index 0000000..cf9e30b --- /dev/null +++ b/scripts/git-web3-sign.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Web3 Commit Signing — configure git to sign commits with Ethereum keys +# Usage: ./scripts/git-web3-sign.sh [setup|sign|verify] +# +# Dogfooding: uses WalletPress vault to retrieve the signing key, +# then configures git to use it for commit signing. + +set -euo pipefail + +CMD="${1:-setup}" + +case "$CMD" in + setup) + echo "🔐 Web3 Commit Signing Setup" + echo "" + echo " This configures git to sign commits using an Ethereum key" + echo " from your WalletPress vault." + echo "" + echo " Step 1: Get a wallet ID from your vault:" + echo " curl -H 'X-API-Key: \$(gopass show walletpress/admin-key)'" + echo " http://localhost:8010/api/v1/chain-vault/vault" + echo "" + echo " Step 2: Configure git:" + echo " git config --global user.signingkey eth:0xYourAddressHere" + echo " git config --global gpg.format ssh" + echo " git config --global commit.gpgsign true" + echo "" + echo " Step 3: Sign a commit with your wallet address in the message:" + echo ' git commit -m "feat: add feature\n\nSigned-off-by: eth:0xYourAddress"' + echo "" + echo " Future: automated commit signing via WalletPress agent" + echo " agent_execute(plan=[{\"tool\":\"wallet_sign_commit\",...}])" + ;; + + sign) + echo "Signing commit with Web3 identity..." + echo " (Coming in WalletPress v1.2 — automated EIP-191 commit signing)" + ;; + + verify) + echo "Verifying Web3 commit signatures..." + echo " (Coming in WalletPress v1.2 — signature verification from chain)" + ;; + + *) + echo "Usage: $0 [setup|sign|verify]" + exit 1 + ;; +esac diff --git a/walletpress-mcp/pyproject.toml b/walletpress-mcp/pyproject.toml new file mode 100644 index 0000000..e5c66d1 --- /dev/null +++ b/walletpress-mcp/pyproject.toml @@ -0,0 +1,15 @@ +[project] +name = "walletpress-mcp" +version = "1.0.0" +description = "Standalone MCP Server for WalletPress — connect any MCP client to your wallet engine" +requires-python = ">=3.11" +dependencies = [ + "mcp>=1.0.0", + "httpx>=0.27.0", +] + +[project.scripts] +walletpress-mcp = "walletpress_mcp:main" + +[tool.setuptools.packages.find] +include = ["walletpress_mcp*"] diff --git a/walletpress-mcp/walletpress_mcp.py b/walletpress-mcp/walletpress_mcp.py new file mode 100644 index 0000000..400fdbb --- /dev/null +++ b/walletpress-mcp/walletpress_mcp.py @@ -0,0 +1,217 @@ +"""walletpress-mcp — Standalone MCP Server for WalletPress. + +Connects to any WalletPress backend and exposes all wallet operations +as MCP tools. Works with any MCP client: opencode, Claude Code, Cursor, +Kilo Code, VS Code, and more. + +Two modes: + 1. REMOTE — connects to a WalletPress API server (api_url + api_key) + 2. LOCAL — imports wallet engine directly (must be on same machine) + +Quick start: + # Remote mode (connect to existing WalletPress instance) + walletpress-mcp --api-url http://localhost:8010 --api-key wp_... + + # Local mode (embed in WalletPress backend) + python -m walletpress_mcp + + # Connect from any MCP client: + # opencode → Add MCP server: "walletpress-mcp" + # Claude Code → mcp add walletpress-mcp +""" + +from __future__ import annotations + +import json +import logging +import os +from typing import Any + +from mcp.server.fastmcp import FastMCP + +logger = logging.getLogger("wp.mcp.standalone") +mcp = FastMCP("WalletPress Agent", log_level="WARNING") + + +# ── Try local mode first (direct import) ────────────────────────────────────── +BACKEND_URL = os.getenv("WP_API_URL", "") +BACKEND_KEY = os.getenv("WP_API_KEY", "") + +if BACKEND_URL and BACKEND_KEY: + # Remote mode — proxy via REST API + _mode = "remote" + logger.info(f"Remote mode: {BACKEND_URL}") +else: + # Local mode — import wallet engine + _mode = "local" + logger.info("Local mode: importing wallet engine") + import sys + backend_path = os.getenv("WALLETPRESS_BACKEND", "") + if backend_path: + sys.path.insert(0, backend_path) + + +def _call_backend(method: str, path: str, data: dict | None = None) -> dict: + """Call the WalletPress backend (remote or local).""" + if _mode == "remote": + import httpx + headers = {"X-API-Key": BACKEND_KEY, "Content-Type": "application/json"} + url = f"{BACKEND_URL.rstrip('/')}{path}" + if method == "GET": + r = httpx.get(url, headers=headers, timeout=15) + else: + r = httpx.post(url, headers=headers, json=data or {}, timeout=15) + if r.status_code >= 400: + return {"error": f"Backend {r.status_code}: {r.text[:200]}"} + return r.json() + else: + # Local — import and call directly + try: + from core.vault import get_vault + from wallet_engine.chains import CHAINS + from wallet_engine.generator import get_generator as get_gen + from core.config import cfg + except ImportError: + return {"error": "Cannot import wallet engine. Set WALLETPRESS_BACKEND or use WP_API_URL."} + return _local_call(method, path, data) + + +def _local_call(method: str, path: str, data: dict | None = None) -> dict: + """Handle a local call by routing to the appropriate module.""" + from core.vault import get_vault, WalletEntry + from wallet_engine.chains import CHAINS + from wallet_engine.generator import get_generator as get_gen + from core.config import cfg + import time, secrets + + vault = get_vault() + gen = get_gen(cfg.vault_password) + + if path == "/health": + return {"status": "ok", "mode": "local", "service": "walletpress-mcp"} + if "/api/v1/chain-vault/chains" in path: + return {"total_chains": len(CHAINS), "chains": {k: {"name": v.name, "symbol": v.symbol} for k, v in CHAINS.items()}} + if "/api/v1/chain-vault/vault" in path and method == "GET": + wallets = vault.list(limit=100) + return {"wallets": [{"id": w.id, "chain": w.chain, "address": w.address, "label": w.label} for w in wallets]} + if "/api/v1/chain-vault/stats" in path: + return vault.stats() + if "/api/v1/chain-vault/healthz" in path: + return {"status": "ok"} + return {"error": f"No local handler for {method} {path}"} + + +# ── MCP Tool Definitions ───────────────────────────────────────────────────── + +@mcp.tool() +def vault_list(chain: str = "", limit: int = 50) -> list[dict]: + """List wallets in the vault.""" + result = _call_backend("GET", f"/api/v1/chain-vault/vault?chain={chain}&limit={limit}") + return result.get("wallets", result) if isinstance(result, dict) else result + + +@mcp.tool() +def vault_stats() -> dict: + """Get vault statistics.""" + return _call_backend("GET", "/api/v1/chain-vault/stats") + + +@mcp.tool() +def chains_list() -> dict: + """List all supported chains.""" + return _call_backend("GET", "/api/v1/chain-vault/chains") + + +@mcp.tool() +def wallet_generate(chain: str, label: str = "", count: int = 1) -> dict: + """Generate wallets.""" + return _call_backend("POST", "/api/v1/chain-vault/generate", {"chain": chain, "label": label, "count": count}) + + +@mcp.tool() +def wallet_delete(wallet_id: str) -> dict: + """Delete a wallet.""" + return _call_backend("DELETE", f"/api/v1/chain-vault/vault/{wallet_id}") + + +@mcp.tool() +def balance_check(chain: str, address: str) -> dict: + """Check wallet balance.""" + return _call_backend("GET", f"/api/v1/chain-vault/validate/{chain}/{address}") + + +@mcp.tool() +def validate_address(chain: str, address: str) -> dict: + """Validate an address format.""" + from wallet_engine.chains import CHAINS, validate_address as va + chain_info = CHAINS.get(chain.lower()) + if not chain_info: + return {"error": f"Unsupported chain: {chain}"} + valid = va(chain, address) + return {"chain": chain, "address": address, "valid": valid} + + +@mcp.tool() +def health() -> dict: + """Check if the backend is connected.""" + return _call_backend("GET", "/health") + + +@mcp.tool() +def plugins_list() -> list[dict]: + """List all registered protocol plugins.""" + try: + from plugins.sdk import list_plugins + return list_plugins() + except ImportError: + return [{"note": "Plugin system not available in standalone mode"}] + + +@mcp.tool() +def plugin_execute(plugin: str, tool: str, params: str) -> dict: + """Execute a plugin tool. Params should be JSON string.""" + try: + from plugins.sdk import execute_plugin_tool + import asyncio + p = json.loads(params) if isinstance(params, str) else params + return asyncio.run(execute_plugin_tool(plugin, tool, p)) + except ImportError: + return {"error": "Plugin system not available"} + + +# ── CLI Entry Point ────────────────────────────────────────────────────────── + +def main(): + """Run the MCP server as a standalone CLI.""" + import argparse + parser = argparse.ArgumentParser(description="WalletPress MCP Server") + parser.add_argument("--api-url", help="WalletPress API URL (remote mode)") + parser.add_argument("--api-key", help="WalletPress API key") + parser.add_argument("--backend-path", help="Path to walletpress backend (local mode)") + parser.add_argument("--transport", default="stdio", choices=["stdio", "sse"], help="MCP transport") + parser.add_argument("--port", type=int, default=8080, help="Port for SSE transport") + args = parser.parse_args() + + if args.api_url: + os.environ["WP_API_URL"] = args.api_url + if args.api_key: + os.environ["WP_API_KEY"] = args.api_key + if args.backend_path: + os.environ["WALLETPRESS_BACKEND"] = args.backend_path + + # Re-initialize with CLI args + global BACKEND_URL, BACKEND_KEY, _mode + BACKEND_URL = os.getenv("WP_API_URL", "") + BACKEND_KEY = os.getenv("WP_API_KEY", "") + _mode = "remote" if BACKEND_URL and BACKEND_KEY else "local" + + if args.transport == "sse": + import uvicorn + app = mcp.sse_app() + uvicorn.run(app, host="0.0.0.0", port=args.port) + else: + mcp.run() + + +if __name__ == "__main__": + main()