#
tokens: 22375/50000 21/21 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .changeset
│   ├── config.json
│   └── README.md
├── .github
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── scripts
│   │   ├── update-mcp-version.sh
│   │   ├── validate-build.sh
│   │   ├── validate-no-version-change.sh
│   │   └── validate-version-increment.sh
│   └── workflows
│       ├── on-pr-feature-branch.yml
│       └── on-push-main.yml
├── .gitignore
├── CHANGELOG.md
├── changeset-status.json
├── CONTRIBUTING.md
├── Dockerfile
├── glama.json
├── index.js
├── LICENSE
├── package.json
├── pnpm-lock.yaml
├── PRIVACY.md
├── README.md
└── smithery.yaml
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | node_modules
2 | .DS_STORE
3 | notes.txt
4 | 
```

--------------------------------------------------------------------------------
/.changeset/README.md:
--------------------------------------------------------------------------------

```markdown
1 | # Changesets
2 | 
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 | 
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | <div align="center">
  2 |     <h1 align="center">CoinMarketCap MCP Server</h1>
  3 |     <p align=center>
  4 |         <a href="https://badge.fury.io/js/@shinzolabs%2Fcoinmarketcap-mcp"><img src="https://badge.fury.io/js/@shinzolabs%2Fcoinmarketcap-mcp.svg" alt="NPM Version"></a>
  5 |         <a href="https://github.com/shinzo-labs/coinmarketcap-mcp/stargazers"><img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.github.com%2Frepos%2Fshinzo-labs%2Fcoinmarketcap-mcp%2Fstargazers&query=%24.length&logo=github&label=stars&color=e3b341" alt="Stars"></a>
  6 |         <a href="https://github.com/shinzo-labs/coinmarketcap-mcp/forks"><img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.github.com%2Frepos%2Fshinzo-labs%2Fcoinmarketcap-mcp%2Fforks&query=%24.length&logo=github&label=forks&color=8957e5" alt="Forks"></a>
  7 |         <a href="https://smithery.ai/server/@shinzo-labs/coinmarketcap-mcp"><img src="https://smithery.ai/badge/@shinzo-labs/coinmarketcap-mcp" alt="Smithery Calls"></a>
  8 |         <a href="https://www.npmjs.com/package/@shinzolabs/coinmarketcap-mcp"><img src="https://img.shields.io/npm/dm/%40shinzolabs%2Fcoinmarketcap-mcp" alt="NPM Downloads"></a>
  9 | </div>
 10 | 
 11 | A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) server implementation for the [CoinMarketCap](https://coinmarketcap.com/) API, providing a standardized interface for accessing cryptocurrency market data, exchange information, and other blockchain-related metrics.
 12 | 
 13 | <p align="center"><img height="512" src=https://github.com/user-attachments/assets/4b2d5f1c-2868-4b9b-8be3-b8ad02b4d331></p>
 14 | 
 15 | ## Features
 16 | 
 17 | - Complete coverage of the CoinMarketCap API
 18 | - Fetch data on the latest crypto trends, market movements, and global market metrics
 19 | - Access to detailed OHLCV data with Standard subscription or higher
 20 | - Type-safe parameter validation with [Zod](https://zod.dev/)
 21 | 
 22 | ## Prerequisites
 23 | 
 24 | If you don't have an API key, first sign up to receive a free `Basic` key [here](https://pro.coinmarketcap.com/signup/?plan=0).
 25 | 
 26 | ## Client Configuration
 27 | 
 28 | There are several options to configure your MCP client with the server. For hosted/remote server setup, use Smithery's CLI with a [Smithery API Key](https://smithery.ai/docs/registry#registry-api). For local installation, use `npx` or build from source. Each of these options is explained below.
 29 | 
 30 | ### Smithery Remote Server (Recommended)
 31 | 
 32 | To add a remote server to your MCP client `config.json`, run the following command from [Smithery CLI](https://github.com/smithery-ai/cli?tab=readme-ov-file#smithery-cli--):
 33 | 
 34 | ```bash
 35 | npx -y @smithery/cli install @shinzo-labs/coinmarketcap-mcp
 36 | ```
 37 | 
 38 | Enter your `COINMARKETCAP_API_KEY` and `SUBSCRIPTION_LEVEL` (see options below) when prompted.
 39 | 
 40 | ### Smithery SDK
 41 | 
 42 | If you are developing your own agent application, you can use the boilerplate code [here](https://smithery.ai/server/@shinzo-labs/coinmarketcap-mcp/api).
 43 | 
 44 | ### NPX Local Install
 45 | 
 46 | To install the server locally with `npx`, add the following to your MCP client `config.json`:
 47 | ```javascript
 48 | {
 49 |   "mcpServers": {
 50 |     "coinmarketcap": {
 51 |       "command": "npx",
 52 |       "args": [
 53 |         "@shinzolabs/coinmarketcap-mcp"
 54 |       ],
 55 |       "env": {
 56 |         "COINMARKETCAP_API_KEY": "your-key-here",
 57 |         "SUBSCRIPTION_LEVEL": "Basic" // See options below
 58 |       }
 59 |     }
 60 |   }
 61 | }
 62 | ```
 63 | 
 64 | ### Build from Source
 65 | 
 66 | 1. Download the repo:
 67 | ```bash
 68 | git clone https://github.com/shinzo-labs/coinmarketcap-mcp.git
 69 | ```
 70 | 
 71 | 2. Install packages (inside cloned repo):
 72 | ```bash
 73 | pnpm i
 74 | ```
 75 | 
 76 | 3. Add the following to your MCP client `config.json`:
 77 | ```javascript
 78 | {
 79 |   "mcpServers": {
 80 |     "coinmarketcap": {
 81 |       "command": "node",
 82 |       "args": [
 83 |         "/path/to/coinmarketcap-mcp/index.js"
 84 |       ],
 85 |       "env": {
 86 |         "COINMARKETCAP_API_KEY": "your-key-here",
 87 |         "SUBSCRIPTION_LEVEL": "Basic" // See options below
 88 |       }
 89 |     }
 90 |   }
 91 | }
 92 | ```
 93 | 
 94 | ## Config Variables
 95 | 
 96 | | Variable                | Description                                                                 | Required? | Default |
 97 | |-------------------------|-----------------------------------------------------------------------------|-----------|---------|
 98 | | `COINMARKETCAP_API_KEY` | API Key from CoinMarketCap.com                                              | Yes       |         |
 99 | | `SUBSCRIPTION_LEVEL`    | `Basic`, `Hobbyist`, `Startup`, `Standard`, `Professional`, or `Enterprise` | No        | `Basic` |
100 | | `PORT`                  | Port for Streamable HTTP transport method                                   | No        | `3000`  |
101 | | `TELEMETRY_ENABLED`     | Enable telemetry                                                            | No        | `true`  |
102 | 
103 | ## Supported Tools
104 | 
105 | ### Subscription Level: Basic (and above)
106 | 
107 | #### Cryptocurrency
108 | - `cryptoCurrencyMap`: Get mapping of all cryptocurrencies
109 | - `getCryptoMetadata`: Get metadata for one or more cryptocurrencies
110 | - `allCryptocurrencyListings`: Get latest market quote for 1-5000 cryptocurrencies
111 | - `cryptoQuotesLatest`: Get latest market quote for 1 or more cryptocurrencies
112 | - `cryptoCategories`: Get list of all cryptocurrency categories
113 | - `cryptoCategory`: Get metadata about a cryptocurrency category
114 | 
115 | #### Exchange
116 | - `exchangeMap`: Get mapping of all exchanges
117 | - `exchangeInfo`: Get metadata for one or more exchanges
118 | - `exchangeAssets`: Get list of all assets available on an exchange
119 | 
120 | #### DEX
121 | - `dexInfo`: Get metadata for one or more decentralised exchanges
122 | - `dexListingsLatest`: Get latest market data for all DEXes
123 | - `dexNetworksList`: Get list of all networks with unique IDs
124 | - `dexSpotPairsLatest`: Get latest market data for all active DEX spot pairs
125 | - `dexPairsQuotesLatest`: Get latest market quotes for spot pairs
126 | - `dexPairsOhlcvLatest`: Get latest OHLCV data for spot pairs
127 | - `dexPairsOhlcvHistorical`: Get historical OHLCV data for spot pairs
128 | - `dexPairsTradeLatest`: Get latest trades for spot pairs
129 | 
130 | #### Global Metrics
131 | - `globalMetricsLatest`: Get latest global cryptocurrency metrics
132 | 
133 | #### Index
134 | - `cmc100IndexLatest`: Get latest CoinMarketCap 100 Index value and constituents
135 | - `cmc100IndexHistorical`: Get historical CoinMarketCap 100 Index values
136 | 
137 | #### Tools
138 | - `priceConversion`: Convert an amount of one cryptocurrency or fiat currency into another
139 | - `getPostmanCollection`: Get Postman collection for the API
140 | 
141 | #### Other
142 | - `fiatMap`: Get mapping of all fiat currencies
143 | - `keyInfo`: Get API key usage and status
144 | - `fearAndGreedLatest`: Get latest Fear & Greed Index
145 | - `fearAndGreedHistorical`: Get historical Fear & Greed Index values
146 | 
147 | ### Subscription Level: Hobbyist (and above)
148 | 
149 | #### Cryptocurrency
150 | - `cryptoAirdrops`: Get list of all cryptocurrency airdrops
151 | - `cryptoAirdrop`: Get metadata about a specific airdrop
152 | - `historicalCryptocurrencyListings`: Get historical market quotes for any cryptocurrency
153 | - `cryptoQuotesHistorical`: Get historical market quotes for any cryptocurrency
154 | - `cryptoQuotesHistoricalV3`: Get historical market quotes with advanced time-based intervals
155 | 
156 | #### Exchange
157 | - `exchangeQuotesHistorical`: Get historical quotes for any exchange
158 | 
159 | #### Global Metrics
160 | - `globalMetricsHistorical`: Get historical global cryptocurrency metrics
161 | 
162 | ### Subscription Level: Startup (and above)
163 | 
164 | #### Cryptocurrency
165 | - `newCryptocurrencyListings`: Get list of most recently added cryptocurrencies
166 | - `cryptoTrendingGainersLosers`: Get biggest gainers and losers in a given time period
167 | - `cryptoTrendingLatest`: Get top cryptocurrencies by search volume
168 | - `cryptoTrendingMostVisited`: Get most visited cryptocurrencies
169 | - `cryptoOhlcvLatest`: Get latest OHLCV market data for any cryptocurrency
170 | - `cryptoOhlcvHistorical`: Get historical OHLCV market data for any cryptocurrency
171 | - `cryptoPricePerformanceStatsLatest`: Get price performance statistics for any cryptocurrency
172 | 
173 | ### Subscription Level: Standard (and above)
174 | 
175 | #### Cryptocurrency
176 | - `cryptoMarketPairsLatest`: Get latest market pairs for any cryptocurrency
177 | 
178 | #### Exchange
179 | - `exchangeListingsLatest`: Get latest market data for all exchanges
180 | - `exchangeMarketPairsLatest`: Get latest market pairs for any exchange
181 | - `exchangeQuotesLatest`: Get latest market quotes for one or more exchanges
182 | 
183 | #### Content
184 | - `contentLatest`: Get latest cryptocurrency news and content
185 | - `contentPostsTop`: Get top cryptocurrency posts
186 | - `contentPostsLatest`: Get latest cryptocurrency posts
187 | - `contentPostsComments`: Get comments for a specific post
188 | 
189 | #### Community
190 | - `communityTrendingTopic`: Get trending topics in the cryptocurrency community
191 | - `communityTrendingToken`: Get trending tokens in the cryptocurrency community
192 | 
193 | ### Subscription Level: Enterprise (and above)
194 | 
195 | #### Blockchain
196 | - `blockchainStatisticsLatest`: Get latest statistics for one or more blockchains
197 | 
198 | ## Contributing
199 | 
200 | Contributions are welcomed and encouraged! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines on issues, contributions, and contact information.
201 | 
202 | ## Data Collection and Privacy
203 | 
204 | Shinzo Labs collects limited anonymous telemetry from this server to help improve our products and services. No personally identifiable information is collected as part of this process. Please review the [Privacy Policy](./PRIVACY.md) for more details on the types of data collected and how to opt-out of this telemetry.
205 | 
206 | ## License
207 | 
208 | MIT
209 | 
```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Contributing to CoinMarketCap MCP
 2 | 
 3 | ## Overview
 4 | 
 5 | Contributions to this codebase are welcomed and appreciated. We encourage novice and professional developers alike to help improve the quality of our software, which is offered as a benefit to the open source community.
 6 | 
 7 | ## Guidelines
 8 | 
 9 | ### Issues
10 | 
11 | If you would like to raise any issues, please do so in the [Issues](https://github.com/shinzo-labs/coinmarketcap-mcp/issues) section and a core contributor will respond in a timely manner. Issue threads may be closed if there are no additional comments added within 7 days of the last update on the thread.
12 | 
13 | ### Code Contributions
14 | 
15 | If you would like to contribute code to the codebase, please contact [email protected] to discuss what feature you would like to add, or what features/bugs you may be able to own from the queue. The steps to then contribute would be:
16 | 1. Create a fork version of the repo.
17 | 2. Open a branch with a name prefixed with `feat/`, `fix/`, or `chore/`.
18 | 3. Implement the desired changes.
19 | 4. Run `npx @changesets/cli` to add a `changeset` for each distinct change in your feature. Read the [changeset README](.changeset/README.md) for more info.
20 | 4. Open a Pull Request from your forked repo back to the main repo. Tag one of the core contributors as a reviewer.
21 | 5. Once the core contributor has reviewed the code and all comments have been resolved, the PR will be approved and merged into the `main` branch.
22 | 6. Merged changes will be added to a versioned package release on a regular schedule.
23 | 
24 | ## Contact
25 | 
26 | If you have any questions or comments about the guidelines here or anything else about the software, please contact [email protected] or open an issue.
27 | 
```

--------------------------------------------------------------------------------
/glama.json:
--------------------------------------------------------------------------------

```json
1 | {
2 |   "$schema": "https://glama.ai/mcp/schemas/server.json",
3 |   "maintainers": [
4 |     "austinborn"
5 |   ]
6 | }
7 | 
```

--------------------------------------------------------------------------------
/.github/scripts/validate-build.sh:
--------------------------------------------------------------------------------

```bash
1 | #! /bin/sh
2 | 
3 | pnpm i --frozen-lockfile
4 | if [ $? -ne 0 ]; then
5 |   echo 'Error: Lockfile is not up to date. Please run `pnpm install` and commit the updated lockfile.'
6 |   exit 1
7 | fi
8 | 
```

--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
 3 |   "changelog": "@changesets/cli/changelog",
 4 |   "commit": false,
 5 |   "fixed": [],
 6 |   "linked": [],
 7 |   "access": "restricted",
 8 |   "baseBranch": "main",
 9 |   "updateInternalDependencies": "patch",
10 |   "ignore": []
11 | }
12 | 
```

--------------------------------------------------------------------------------
/changeset-status.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "changesets": [
 3 |     {
 4 |       "releases": [
 5 |         {
 6 |           "name": "@shinzolabs/coinmarketcap-mcp",
 7 |           "type": "minor"
 8 |         }
 9 |       ],
10 |       "summary": "Update and pin dependencies",
11 |       "id": "five-planets-do"
12 |     }
13 |   ],
14 |   "releases": [
15 |     {
16 |       "name": "@shinzolabs/coinmarketcap-mcp",
17 |       "type": "minor",
18 |       "oldVersion": "1.3.7",
19 |       "changesets": [
20 |         "five-planets-do"
21 |       ],
22 |       "newVersion": "1.4.0"
23 |     }
24 |   ]
25 | }
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
 2 | FROM node:lts-alpine
 3 | 
 4 | # Create app directory in container
 5 | WORKDIR /app
 6 | 
 7 | # Copy package.json and package-lock.json if available
 8 | COPY package*.json ./
 9 | 
10 | # Install dependencies (ignoring scripts to skip any prepare hooks)
11 | RUN npm install --ignore-scripts
12 | 
13 | # Copy the rest of the application code
14 | COPY . .
15 | 
16 | # Expose port if needed (not required for MCP using stdio)
17 | 
18 | # Command to run the application
19 | CMD ["npm", "start"]
20 | 
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | name: Feature request
 3 | about: Suggest a feature for this codebase
 4 | title: ''
 5 | labels: ''
 6 | assignees: austinborn
 7 | 
 8 | ---
 9 | 
10 | # Overview
11 | <!--- Give a brief overview of the feature you would like to see in the codebase. -->
12 | 
13 | # Purpose
14 | <!--- Please explain why the feature would be a meaningful addition and isn't captured by other features already. -->
15 | 
16 | # Example Use Case(s)
17 | <!--- Provide at least one scenario where this feature may add value in practice. -->
18 | 
19 | # Requirements
20 | - [ ] The feature is not covered in any other open issues
21 | - [ ] The feature pertains primarily to this codebase
22 | 
```

--------------------------------------------------------------------------------
/.github/scripts/update-mcp-version.sh:
--------------------------------------------------------------------------------

```bash
 1 | #! /bin/sh
 2 | 
 3 | version=$(jq -r .version package.json)
 4 | 
 5 | git fetch origin changeset-release/main:changeset-release/main
 6 | git checkout changeset-release/main
 7 | 
 8 | sed -i "s/version: \"[0-9]\+\.[0-9]\+\.[0-9]\+\"/version: \"$version\"/g" $PATH_TO_FILE
 9 | if git diff --quiet $PATH_TO_FILE; then
10 |   echo "No changes to MCP version, skipping commit"
11 | else
12 |   git config --global user.email "github-actions[bot]@users.noreply.github.com"
13 |   git config --global user.name "github-actions[bot]"
14 |   git add $PATH_TO_FILE
15 |   git commit -m "Update MCP version to $version"
16 |   git push origin changeset-release/main
17 | fi
18 | 
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
 2 | 
 3 | startCommand:
 4 |   type: http
 5 |   configSchema:
 6 |     # JSON Schema defining the configuration options for the MCP.
 7 |     type: object
 8 |     required:
 9 |       - COINMARKETCAP_API_KEY
10 |     properties:
11 |       COINMARKETCAP_API_KEY:
12 |         type: string
13 |         description: "COINMARKETCAP_API_KEY - Your API key"
14 |       SUBSCRIPTION_LEVEL:
15 |         type: string
16 |         default: Basic
17 |         description: "SUBSCRIPTION_LEVEL - One of: Basic, Hobbyist, Startup, Standard,
18 |           Professional, or Enterprise (defaults to Basic)"
19 |   exampleConfig:
20 |     COINMARKETCAP_API_KEY: test-key-123
21 |     SUBSCRIPTION_LEVEL: Basic
22 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "@shinzolabs/coinmarketcap-mcp",
 3 |   "version": "1.4.5",
 4 |   "description": "A complete MCP for the CoinMarketCap API",
 5 |   "type": "module",
 6 |   "main": "index.js",
 7 |   "bin": {
 8 |     "coinmarketcap-mcp": "./index.js"
 9 |   },
10 |   "scripts": {
11 |     "start": "node ./index.js"
12 |   },
13 |   "dependencies": {
14 |     "@modelcontextprotocol/sdk": "1.16.0",
15 |     "@shinzolabs/instrumentation-mcp": "^1.0.8",
16 |     "@smithery/sdk": "1.4.3",
17 |     "zod": "3.22.4"
18 |   },
19 |   "devDependencies": {
20 |     "@changesets/cli": "2.29.4"
21 |   },
22 |   "keywords": [
23 |     "crypto",
24 |     "mcp",
25 |     "coinmarketcap"
26 |   ],
27 |   "author": "Austin Born ([email protected])",
28 |   "license": "MIT",
29 |   "repository": {
30 |     "type": "git",
31 |     "url": "https://github.com/shinzo-labs/coinmarketcap-mcp"
32 |   },
33 |   "engines": {
34 |     "node": ">=18.0.0"
35 |   }
36 | }
37 | 
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | name: Bug report
 3 | about: Write a report about a problem with functionality
 4 | title: ''
 5 | labels: ''
 6 | assignees: austinborn
 7 | 
 8 | ---
 9 | 
10 | # Overview
11 | <!--- Give a brief overview of the bug or issue. -->
12 | 
13 | # Steps to Reproduce
14 | <!--- Explain how testers can reproduce the issue. Be as specific as reasonably possible about your local environment and relevant setup configuration. -->
15 | 
16 | # Expected Behavior
17 | <!--- Did you expect a particular functionality different from what was observed? -->
18 | 
19 | # Additional Context
20 | <!--- Please provide any other details that can aid in the resolution of the issue, including any screenshots, sample code snippets, or sequences of commands to test. -->
21 | 
22 | # Requirements
23 | - [ ] The bug is not covered in any other open issues
24 | - [ ] The bug pertains primarily to this codebase
25 | - [ ] The submitter has taken reasonable steps to confirm whether the bug is a result of the code itself and not user error.
26 | 
```

--------------------------------------------------------------------------------
/.github/scripts/validate-version-increment.sh:
--------------------------------------------------------------------------------

```bash
 1 | #! /bin/sh
 2 | 
 3 | BASE_BRANCH=${GITHUB_BASE_REF:-main}
 4 | git fetch origin $BASE_BRANCH
 5 | 
 6 | pkg_version=$(jq -r .version package.json)
 7 | base_version=$(git show origin/$BASE_BRANCH:package.json | jq -r .version)
 8 | 
 9 | IFS='.' read -r pkg_major pkg_minor pkg_patch <<< "$pkg_version"
10 | IFS='.' read -r base_major base_minor base_patch <<< "$base_version"
11 | 
12 | inc_count=0
13 | 
14 | if [ "$pkg_major" -eq $((base_major + 1)) ] && [ "$pkg_minor" -eq 0 ] && [ "$pkg_patch" -eq 0 ]; then
15 |   inc_count=1
16 | elif [ "$pkg_major" -eq "$base_major" ] && [ "$pkg_minor" -eq $((base_minor + 1)) ] && [ "$pkg_patch" -eq 0 ]; then
17 |   inc_count=1
18 | elif [ "$pkg_major" -eq "$base_major" ] && [ "$pkg_minor" -eq "$base_minor" ] && [ "$pkg_patch" -eq $((base_patch + 1)) ]; then
19 |   inc_count=1
20 | fi
21 | 
22 | if [ "$inc_count" -ne 1 ]; then
23 |   echo "Error: Version must increment one of major, minor, or patch by 1 (and reset lower segments if major/minor is incremented)."
24 |   echo "Base branch version: $base_version"
25 |   echo "PR version: $pkg_version"
26 |   exit 1
27 | fi
28 | 
```

--------------------------------------------------------------------------------
/.github/scripts/validate-no-version-change.sh:
--------------------------------------------------------------------------------

```bash
 1 | #! /bin/sh
 2 | 
 3 | BASE_BRANCH=${GITHUB_BASE_REF:-main}
 4 | git fetch origin $BASE_BRANCH
 5 | 
 6 | pkg_version=$(jq -r .version package.json)
 7 | base_version=$(git show origin/$BASE_BRANCH:package.json | jq -r .version)
 8 | 
 9 | # Check if version has changed from base branch
10 | if [ "$pkg_version" != "$base_version" ]; then
11 |   echo "Error: Version should not change from base branch."
12 |   echo "Base branch version: $base_version"
13 |   echo "PR version: $pkg_version"
14 |   exit 1
15 | fi
16 | 
17 | # Check if package version matches the version in FILE_PATH
18 | if [ -n "$FILE_PATH" ] && [ -f "$FILE_PATH" ]; then
19 |   file_content=$(cat "$FILE_PATH")
20 |   file_version=$(echo "$file_content" | grep -o 'version: "[0-9]\+\.[0-9]\+\.[0-9]\+"' | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+')
21 |   
22 |   if [ "$pkg_version" != "$file_version" ]; then
23 |     echo "Error: Version in package.json ($pkg_version) does not match version in $FILE_PATH ($file_version)"
24 |     exit 1
25 |   fi
26 | else
27 |   echo "Warning: FILE_PATH not set or file does not exist. Skipping file version check."
28 |   exit 1
29 | fi
30 | 
31 | echo "Version validation passed: $pkg_version"
32 | 
```

--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
 1 | # @shinzolabs/coinmarketcap-mcp
 2 | 
 3 | ## 1.4.5
 4 | 
 5 | ### Patch Changes
 6 | 
 7 | - 2d635ac: README typo
 8 | 
 9 | ## 1.4.4
10 | 
11 | ### Patch Changes
12 | 
13 | - 3b5383f: Patch telemetryEnabled config
14 | 
15 | ## 1.4.3
16 | 
17 | ### Patch Changes
18 | 
19 | - 26f8fa4: Add Privacy Policy and TELEMETRY_ENABLED config option
20 | 
21 | ## 1.4.2
22 | 
23 | ### Patch Changes
24 | 
25 | - 3e4839e: Upgrade to [email protected]
26 | 
27 | ## 1.4.1
28 | 
29 | ### Patch Changes
30 | 
31 | - ceffe92: Instrument tool calls
32 | 
33 | ## 1.4.0
34 | 
35 | ### Minor Changes
36 | 
37 | - 4bf1fa5: Update and pin dependencies
38 | 
39 | ## 1.3.7
40 | 
41 | ### Patch Changes
42 | 
43 | - e01aed7: Update gh workflows to manage release PRs better
44 | 
45 | ## 1.3.6
46 | 
47 | ### Patch Changes
48 | 
49 | - c11e225: Publish NPM package with pnpm
50 | 
51 | ## 1.3.5
52 | 
53 | ### Patch Changes
54 | 
55 | - 582e015: Add correct permissions for NPM token
56 | 
57 | ## 1.3.4
58 | 
59 | ### Patch Changes
60 | 
61 | - b09d521: Use 'npm ci' for npm publish script
62 | 
63 | ## 1.3.3
64 | 
65 | ### Patch Changes
66 | 
67 | - f4046d2: Make npm-publish.sh executable
68 | 
69 | ## 1.3.2
70 | 
71 | ### Patch Changes
72 | 
73 | - 76a5912: Update NPM publish script
74 | 
75 | ## 1.3.1
76 | 
77 | ### Patch Changes
78 | 
79 | - 39d5c10: Add @changesets/cli as dev dep for Release PR creation
80 | - 14a8d0d: Debug release workflow
81 | - 815f99e: Patch create-release-pr
82 | - 2eee98f: Add github workflows for checks and publishing
83 | - 86e6d2f: Ensure MCP version can be updated in same release PRs
84 | 
```

--------------------------------------------------------------------------------
/.github/workflows/on-pr-feature-branch.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: On PR for Feature Branch
 2 | 
 3 | on:
 4 |   pull_request:
 5 |     branches-ignore:
 6 |       - changeset-release/main
 7 | 
 8 | env:
 9 |   UPSTREAM_BRANCH: origin/${{ github.base_ref }}
10 | 
11 | concurrency:
12 |   group: pr-${{ github.event.pull_request.number }}-checks
13 |   cancel-in-progress: true
14 | 
15 | jobs:
16 |   check-changeset:
17 |     name: Check for changeset
18 |     runs-on: ubuntu-latest
19 |     steps:
20 |       - name: Checkout code
21 |         uses: actions/checkout@v4
22 |         with:
23 |           fetch-depth: 0
24 |       - name: Fetch base branch
25 |         run: git fetch origin main:refs/remotes/origin/main
26 |       - name: Install pnpm
27 |         run: npm install -g pnpm
28 |       - name: Install dependencies
29 |         run: pnpm install --frozen-lockfile
30 |       - name: Check for changeset
31 |         run: npx @changesets/cli status --since=origin/main
32 | 
33 |   validate-no-version-change:
34 |     name: Validate the version has not changed
35 |     runs-on: ['ubuntu-latest']
36 |     steps:
37 |       - name: Checkout code
38 |         uses: actions/checkout@v4
39 |       - name: Run validation script
40 |         run: .github/scripts/validate-no-version-change.sh
41 |         env:
42 |           FILE_PATH: index.js
43 | 
44 |   validate-build:
45 |     name: Validate build
46 |     runs-on: ['ubuntu-latest']
47 |     steps:
48 |       - name: Checkout code
49 |         uses: actions/checkout@v4
50 |       - name: Install pnpm
51 |         run: npm install -g pnpm
52 |       - name: Run validation script
53 |         run: .github/scripts/validate-build.sh
54 | 
```

--------------------------------------------------------------------------------
/.github/workflows/on-push-main.yml:
--------------------------------------------------------------------------------

```yaml
  1 | name: On Push to Main Branch
  2 | 
  3 | on:
  4 |   push:
  5 |     branches:
  6 |       - main
  7 | 
  8 | concurrency:
  9 |   group: deployment
 10 |   cancel-in-progress: true
 11 | 
 12 | jobs:
 13 |   check-version:
 14 |     runs-on: ubuntu-latest
 15 |     needs: check-changesets
 16 |     outputs:
 17 |       version_changed: ${{ steps.version-check.outputs.changed }}
 18 |     steps:
 19 |       - uses: actions/checkout@v4
 20 |         with:
 21 |           fetch-depth: 0
 22 |       - name: Check if version changed
 23 |         id: version-check
 24 |         run: |
 25 |           current_version=$(jq -r .version package.json)
 26 |           base_version=$(git show HEAD^:package.json | jq -r .version)
 27 |           if [ "$current_version" != "$base_version" ]; then
 28 |             echo "changed=true" >> $GITHUB_OUTPUT
 29 |           else
 30 |             echo "changed=false" >> $GITHUB_OUTPUT
 31 |           fi
 32 | 
 33 |   check-changesets:
 34 |     runs-on: ubuntu-latest
 35 |     outputs:
 36 |       hasChangesets: ${{ steps.changeset-check.outputs.hasChangesets }}
 37 |     steps:
 38 |       - uses: actions/checkout@v4
 39 |         with:
 40 |           fetch-depth: 0
 41 |       - name: Check for changesets
 42 |         id: changeset-check
 43 |         run: |
 44 |           if [ -n "$(ls .changeset/*.md 2>/dev/null | grep -v 'README.md' | grep -v 'config.json')" ]; then
 45 |             echo "hasChangesets=true" >> $GITHUB_OUTPUT
 46 |           else
 47 |             echo "hasChangesets=false" >> $GITHUB_OUTPUT
 48 |           fi
 49 | 
 50 |   create-release-pr:
 51 |     name: Create or update Release PR
 52 |     runs-on: ubuntu-latest
 53 |     needs: [check-version, check-changesets]
 54 |     if: needs.check-changesets.outputs.hasChangesets == 'true' && needs.check-version.outputs.version_changed == 'false'
 55 |     steps:
 56 |       - name: Checkout code
 57 |         uses: actions/checkout@v4
 58 |       - name: Install pnpm
 59 |         run: npm install -g pnpm
 60 |       - name: Install dependencies
 61 |         run: pnpm install --frozen-lockfile
 62 |       - name: Get next version from changesets
 63 |         id: get-next-version
 64 |         run: |
 65 |           npx changeset status --output changeset-status.json
 66 |           version=$(jq -r '.releases[0].newVersion' changeset-status.json)
 67 |           echo "next_version=$version" >> $GITHUB_OUTPUT
 68 |       - name: Create or update Release PR
 69 |         uses: changesets/action@v1
 70 |         with:
 71 |           publish: false
 72 |           title: "Release v${{ steps.get-next-version.outputs.next_version }}"
 73 |         env:
 74 |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 75 |       - name: Update MCP version
 76 |         run: .github/scripts/update-mcp-version.sh
 77 |         env:
 78 |           PATH_TO_FILE: 'index.js'
 79 | 
 80 |   gh-release:
 81 |     name: GH Release
 82 |     runs-on: ubuntu-latest
 83 |     needs: check-version
 84 |     if: needs.check-version.outputs.version_changed == 'true'
 85 |     env:
 86 |       GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 87 |     steps:
 88 |       - name: Checkout Repo
 89 |         uses: actions/checkout@v4
 90 |       - name: Get release version
 91 |         id: get-version
 92 |         run: |
 93 |           echo "result=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT
 94 |       - name: Get release body
 95 |         run: |
 96 |           # Get the PR body to use in the GH release body
 97 |           gh pr list --search "$(git rev-parse HEAD)" --state merged --json number,body --jq '"This release was merged in PR #" + (.[0].number | tostring) + "\n\n" + (.[0].body | split("# Releases")[1:] | join("# Releases"))' > pr_body.tmp
 98 |       - name: Create release
 99 |         uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
100 |         with:
101 |           tag_name: v${{ steps.get-version.outputs.result }}
102 |           name: Release v${{ steps.get-version.outputs.result }}
103 |           body_path: pr_body.tmp
104 | 
105 |   npm-publish:
106 |     name: Publish to npm registry
107 |     runs-on: ubuntu-latest
108 |     needs: check-version
109 |     if: needs.check-version.outputs.version_changed == 'true'
110 |     permissions:
111 |       contents: read
112 |       id-token: write
113 |     steps:
114 |       - name: Checkout code
115 |         uses: actions/checkout@v4
116 |       - name: Setup Node.js and npm auth
117 |         uses: actions/setup-node@v4
118 |         with:
119 |           node-version: '20.x'
120 |           registry-url: 'https://registry.npmjs.org'
121 |       - name: Install pnpm
122 |         run: npm install -g pnpm
123 |       - name: Install dependencies
124 |         run: pnpm install --frozen-lockfile
125 |       - name: Publish
126 |         run: npm publish --provenance --access public
127 |         env:
128 |           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
129 | 
```

--------------------------------------------------------------------------------
/PRIVACY.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Privacy Policy
 2 | 
 3 | Effective Date: July 24, 2025
 4 | 
 5 | This privacy policy explains how Shinzo Labs collects telemetry data using the [Shinzo platform](https://github.com/shinzo-labs/shinzo-ts) to improve its products and services.
 6 | 
 7 | ## Data Collection
 8 | 
 9 | Shinzo Labs collects limited telemetry data using instrumentation from the [Shinzo platform](https://github.com/shinzo-labs/shinzo-ts) to help us understand usage patterns, detect errors, and improve the tool. Our telemetry pipeline includes steps to anonymize data and remove any personally identifiable information.
10 | 
11 | ### What We Collect
12 | 
13 | To better understand the performance of our product in different environments, we collect trace data on operations with limited attributes, and metrics for operation counts. This includes, but is not limited to, the following data:
14 | - **Span Name**: Name of the operation called
15 | - **Span ID**: Random identifier for the span (ex. `tools/call send_message`)
16 | - **Timestamp**: The time in which the operation started
17 | - **Duration**: The total duration of the operation
18 | - **Service Name**: Name of the server called
19 | - **Service Version**: Specific version of the server
20 | - **Service Instance ID**: Random identifier for this process instance
21 | - **Status Code**: Error code (if any)
22 | - **Host Arch**: Hardware architecture (ex. `arm64`, `amd64`)
23 | - **Method Name**: Capability-specific name (ex. `tools/call`)
24 | - **Request ID**: Random identifier for this specific operation or request
25 | - **Session ID**: Random identifier for the overall running session, so correlated requests in a single session
26 | - **Tool Name**: Name of the tool called (if any, ex. `send_message`)
27 | - **OS Type**: `darwin`, `linux`, `windows`, or `other`
28 | - **OS Version**: Specific OS version
29 | - **Telemetry SDK Language**: ex. `nodejs`
30 | - **Telemetry SDK Name**: ex. `opentelemetry`
31 | - **Telemetry SDK Version**: Specific telemetry SDK version
32 | - **Error Code**: Whether an operation was successful or not
33 | - **Error Message**: Short description of the error message
34 | 
35 | ### What We DO NOT Collect
36 | 
37 | We explicitly DO NOT collect:
38 | - **Personal information (PII)**: Any personally identifiable information
39 | - **IP addresses and Ports**: Your network information
40 | - **Usernames**: System or account usernames
41 | - **Arguments for operations**: Any of the data entered by the user as arguments or parameters for operations
42 | 
43 | ## Data Usage
44 | 
45 | The collected data is used for:
46 | 
47 | - Understanding how the product is used
48 | - Identifying common errors or issues
49 | - Measuring feature adoption and performance
50 | - Guiding development priorities
51 | - Improving overall user experience
52 | 
53 | ## Privacy Protection
54 | 
55 | We take your privacy seriously:
56 | 
57 | - All IDs are randomly-generated UUIDs, not derived from your machine hardware ID
58 | - All data is sent securely via HTTPS to collectors controlled by Shinzo Labs or affiliate third parties (current infrastructure may be shared upon request)
59 | - Data is only used in aggregate form for statistical analysis
60 | - We implement robust sanitization of all data to ensure any potential PII is never included in telemetry
61 | - We maintain data minimization principles - only collecting essential data for explicit purposes
62 | - All telemetry is processed in a way that does not connect it to specific individuals
63 | 
64 | ## Data Retention
65 | 
66 | Telemetry data is retained for a period of 12 months, after which it is automatically deleted from Shinzo Labs' collector infrastructure
67 | 
68 | ## User Control
69 | 
70 | Telemetry is enabled by default, but you can disable it at any time by setting `TELEMETRY_ENABLED: "false"` in the environment config. In doing so, no telemetry data will otherwise be shared with Shinzo Labs without the user's explicit permission.
71 | 
72 | ## Legal Basis
73 | 
74 | We collect this data based on our legitimate interest (GDPR Article 6(1)(f)) to improve our software. Since we use randomly-generated UUIDs rather than personal identifiers, the privacy impact is minimal while allowing us to gather important usage data.
75 | 
76 | ## Changes to This Policy
77 | 
78 | We may update this privacy policy from time to time. Any changes will be posted in this document and in release notes.
79 | 
80 | ## Contact
81 | 
82 | If you have any questions about this privacy policy or our data practices, please contact [Shinzo Labs](mailto:[email protected]).
83 | 
84 | ## Acknowledgements
85 | 
86 | This policy is adapted from the [Privacy Policy for DesktopCommanderMCP](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/PRIVACY.md), although Shinzo Labs has no affiliation with the team behind DesktopCommanderMCP.
87 | 
```

--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------

```javascript
   1 | #!/usr/bin/env node
   2 | 
   3 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
   4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
   5 | import { createStatefulServer } from "@smithery/sdk/server/stateful.js"
   6 | import { instrumentServer } from "@shinzolabs/instrumentation-mcp"
   7 | import { z } from "zod"
   8 | 
   9 | // Subscription plan levels in order of increasing access
  10 | const SUBSCRIPTION_LEVELS = {
  11 |   "Basic": 0,
  12 |   "Hobbyist": 1,
  13 |   "Startup": 2,
  14 |   "Standard": 3,
  15 |   "Professional": 4,
  16 |   "Enterprise": 5
  17 | }
  18 | 
  19 | // Enhanced response formatter with status code support
  20 | function formatErrorResponse(message, status = 403) {
  21 |   return {
  22 |     content: [{
  23 |       type: "text",
  24 |       text: JSON.stringify({ error: message, status })
  25 |     }]
  26 |   }
  27 | }
  28 | 
  29 | // Helper function to format successful API responses
  30 | function formatResponse(data) {
  31 |   return {
  32 |     content: [{
  33 |       type: "text",
  34 |       text: JSON.stringify(data)
  35 |     }]
  36 |   }
  37 | }
  38 | 
  39 | // Enhanced API request wrapper with error handling
  40 | async function makeApiRequestWithErrorHandling(apiKey, endpoint, params = {}) {
  41 |   try {
  42 |     const data = await makeApiRequest(apiKey, endpoint, params)
  43 |     return formatResponse(data)
  44 |   } catch (error) {
  45 |     return formatErrorResponse(`Error fetching data from CoinMarketCap: ${error.message}`, 500)
  46 |   }
  47 | }
  48 | 
  49 | // Helper function for making API requests to CoinMarketCap
  50 | async function makeApiRequest(apiKey, endpoint, params = {}) {
  51 |   const queryParams = new URLSearchParams()
  52 |   Object.entries(params).forEach(([key, value]) => {
  53 |     if (value !== undefined) {
  54 |       queryParams.append(key, value.toString())
  55 |     }
  56 |   })
  57 | 
  58 |   const url = `https://pro-api.coinmarketcap.com${endpoint}${queryParams.toString() ? `?${queryParams.toString()}` : ''}`
  59 | 
  60 |   const response = await fetch(url, {
  61 |     method: 'GET',
  62 |     headers: {
  63 |       'Accept': 'application/json',
  64 |       'X-CMC_PRO_API_KEY': apiKey,
  65 |     }
  66 |   })
  67 | 
  68 |   if (!response.ok) {
  69 |     throw new Error(`Error fetching data from CoinMarketCap: ${response.statusText}`)
  70 |   }
  71 | 
  72 |   return await response.json()
  73 | }
  74 | 
  75 | // Helper function to check subscription level at runtime
  76 | function checkSubscriptionLevel(subscriptionLevel, requiredLevel) {
  77 |   const currentLevel = SUBSCRIPTION_LEVELS[subscriptionLevel]
  78 |   return currentLevel >= requiredLevel
  79 | }
  80 | 
  81 | // Wrapper function to handle common endpoint patterns
  82 | async function handleEndpoint(apiCall) {
  83 |   try {
  84 |     return await apiCall()
  85 |   } catch (error) {
  86 |     return formatErrorResponse(error.message, error.status || 403)
  87 |   }
  88 | }
  89 | 
  90 | function getConfig(config) {
  91 |   return {
  92 |     apiKey: config?.COINMARKETCAP_API_KEY || process.env.COINMARKETCAP_API_KEY,
  93 |     subscriptionLevel: config?.SUBSCRIPTION_LEVEL || process.env.SUBSCRIPTION_LEVEL || 'Basic',
  94 |     telemetryEnabled: config?.TELEMETRY_ENABLED || process.env.TELEMETRY_ENABLED || "true"
  95 |   }
  96 | }
  97 | 
  98 | const serverInfo = {
  99 |   name: "CoinMarketCap-MCP",
 100 |   version: "1.4.5",
 101 |   description: "A complete MCP for the CoinMarketCap API"
 102 | }
 103 | 
 104 | function createServer({ config }) {
 105 |   const server = new McpServer(serverInfo)
 106 | 
 107 |   const { apiKey, subscriptionLevel, telemetryEnabled } = getConfig(config)
 108 | 
 109 |   if (telemetryEnabled !== "false") {
 110 |     const telemetry = instrumentServer(server, {
 111 |       serverName: serverInfo.name,
 112 |       serverVersion: serverInfo.version,
 113 |       exporterEndpoint: "https://api.otel.shinzo.tech/v1",
 114 |     })
 115 |   }
 116 | 
 117 |   /*
 118 |   * BASIC SUBSCRIPTION ENDPOINTS
 119 |   */
 120 |   if (checkSubscriptionLevel(subscriptionLevel, SUBSCRIPTION_LEVELS.Basic)) {
 121 |     // /cryptocurrency/categories
 122 |     server.tool("cryptoCategories",
 123 |       "Returns information about all coin categories available on CoinMarketCap.",
 124 |       {
 125 |         start: z.number().optional(),
 126 |         limit: z.number().optional(),
 127 |         id: z.string().optional(),
 128 |         slug: z.string().optional(),
 129 |         symbol: z.string().optional()
 130 |       },
 131 |       async (params) => {
 132 |         return handleEndpoint(async () => {
 133 |           const data = await makeApiRequest(apiKey, '/v1/cryptocurrency/categories', params)
 134 |           return formatResponse(data)
 135 |         })
 136 |       }
 137 |     )
 138 | 
 139 |     // /cryptocurrency/category
 140 |     server.tool("cryptoCategory",
 141 |       "Returns information about a single coin category on CoinMarketCap.",
 142 |       {
 143 |         id: z.string(),
 144 |         start: z.number().optional(),
 145 |         limit: z.number().optional(),
 146 |         convert: z.string().optional(),
 147 |         convert_id: z.string().optional()
 148 |       },
 149 |       async (params) => {
 150 |         return handleEndpoint(async () => {
 151 |           const data = await makeApiRequest(apiKey, '/v1/cryptocurrency/category', params)
 152 |           return formatResponse(data)
 153 |         })
 154 |       }
 155 |     )
 156 | 
 157 |     // /cryptocurrency/map
 158 |     server.tool("cryptoCurrencyMap",
 159 |       "Returns a mapping of all cryptocurrencies to unique CoinMarketCap IDs.",
 160 |       {
 161 |         listing_status: z.string().optional(),
 162 |         start: z.number().optional(),
 163 |         limit: z.number().optional(),
 164 |         sort: z.string().optional(),
 165 |         symbol: z.string().optional(),
 166 |         aux: z.string().optional()
 167 |       },
 168 |       async ({ listing_status = 'active', start = 1, limit = 100, sort = 'id', symbol, aux }) => {
 169 |         return handleEndpoint(async () => {
 170 |           return await makeApiRequestWithErrorHandling(apiKey, '/v1/cryptocurrency/map', {
 171 |             listing_status,
 172 |             start,
 173 |             limit,
 174 |             sort,
 175 |             symbol,
 176 |             aux
 177 |           })
 178 |         })
 179 |       }
 180 |     )
 181 | 
 182 |     // /cryptocurrency/info
 183 |     server.tool("getCryptoMetadata",
 184 |       "Returns all static metadata for one or more cryptocurrencies including logo, description, and website URLs.",
 185 |       {
 186 |         symbol: z.string().optional(),
 187 |         id: z.string().optional(),
 188 |         slug: z.string().optional(),
 189 |         address: z.string().optional(),
 190 |         aux: z.string().optional(),
 191 |         skip_invalid: z.boolean().optional()
 192 |       },
 193 |       async ({ symbol, id, slug, address, aux, skip_invalid }) => {
 194 |         return handleEndpoint(async () => {
 195 |           const data = await makeApiRequest(apiKey, '/v2/cryptocurrency/info', {
 196 |             symbol,
 197 |             id,
 198 |             slug,
 199 |             address,
 200 |             aux,
 201 |             skip_invalid
 202 |           })
 203 |           return formatResponse(data)
 204 |         })
 205 |       }
 206 |     )
 207 | 
 208 |     // /cryptocurrency/listings/latest
 209 |     server.tool("allCryptocurrencyListings",
 210 |       "Returns a paginated list of all active cryptocurrencies with latest market data.",
 211 |       {
 212 |         start: z.number().optional(),
 213 |         limit: z.number().min(1).max(5000).optional(),
 214 |         price_min: z.number().optional(),
 215 |         price_max: z.number().optional(),
 216 |         market_cap_min: z.number().optional(),
 217 |         market_cap_max: z.number().optional(),
 218 |         volume_24h_min: z.number().optional(),
 219 |         volume_24h_max: z.number().optional(),
 220 |         circulating_supply_min: z.number().optional(),
 221 |         circulating_supply_max: z.number().optional(),
 222 |         percent_change_24h_min: z.number().optional(),
 223 |         percent_change_24h_max: z.number().optional(),
 224 |         convert: z.string().optional(),
 225 |         convert_id: z.string().optional(),
 226 |         sort: z.enum(['market_cap', 'name', 'symbol', 'date_added', 'price', 'circulating_supply', 'total_supply', 'max_supply', 'num_market_pairs', 'volume_24h', 'percent_change_1h', 'percent_change_24h', 'percent_change_7d']).optional(),
 227 |         sort_dir: z.enum(['asc', 'desc']).optional(),
 228 |         cryptocurrency_type: z.string().optional(),
 229 |         tag: z.string().optional(),
 230 |         aux: z.string().optional()
 231 |       },
 232 |       async (params) => {
 233 |         return handleEndpoint(async () => {
 234 |           const data = await makeApiRequest(apiKey, '/v1/cryptocurrency/listings/latest', params)
 235 |           return formatResponse(data)
 236 |         })
 237 |       }
 238 |     )
 239 | 
 240 |     // /cryptocurrency/quotes/latest
 241 |     server.tool("cryptoQuotesLatest",
 242 |       "Returns the latest market quote for one or more cryptocurrencies.",
 243 |       {
 244 |         id: z.string().optional(),
 245 |         slug: z.string().optional(),
 246 |         symbol: z.string().optional(),
 247 |         convert: z.string().optional(),
 248 |         convert_id: z.string().optional(),
 249 |         aux: z.string().optional(),
 250 |         skip_invalid: z.boolean().optional()
 251 |       },
 252 |       async (params) => {
 253 |         return handleEndpoint(async () => {
 254 |           const data = await makeApiRequest(apiKey, '/v2/cryptocurrency/quotes/latest', params)
 255 |           return formatResponse(data)
 256 |         })
 257 |       }
 258 |     )
 259 | 
 260 |     // /v4/dex/listings/info
 261 |     server.tool("dexInfo",
 262 |       "Returns all static metadata for one or more decentralised exchanges.",
 263 |       {
 264 |         id: z.string().optional(),
 265 |         aux: z.string().optional()
 266 |       },
 267 |       async (params) => {
 268 |         return handleEndpoint(async () => {
 269 |           const data = await makeApiRequest(apiKey, '/v4/dex/listings/info', params)
 270 |           return formatResponse(data)
 271 |         })
 272 |       }
 273 |     )
 274 | 
 275 |     // /v4/dex/listings/quotes
 276 |     server.tool("dexListingsLatest",
 277 |       "Returns a paginated list of all decentralised cryptocurrency exchanges including the latest aggregate market data.",
 278 |       {
 279 |         start: z.string().optional(),
 280 |         limit: z.string().optional(),
 281 |         sort: z.enum(['name', 'volume_24h', 'market_share', 'num_markets']).optional(),
 282 |         sort_dir: z.enum(['desc', 'asc']).optional(),
 283 |         type: z.enum(['all', 'orderbook', 'swap', 'aggregator']).optional(),
 284 |         aux: z.string().optional(),
 285 |         convert_id: z.string().optional()
 286 |       },
 287 |       async (params) => {
 288 |         return handleEndpoint(async () => {
 289 |           const data = await makeApiRequest(apiKey, '/v4/dex/listings/quotes', params)
 290 |           return formatResponse(data)
 291 |         })
 292 |       }
 293 |     )
 294 | 
 295 |     // /v4/dex/networks/list
 296 |     server.tool("dexNetworksList",
 297 |       "Returns a list of all networks to unique CoinMarketCap ids.",
 298 |       {
 299 |         start: z.string().optional(),
 300 |         limit: z.string().optional(),
 301 |         sort: z.enum(['id', 'name']).optional(),
 302 |         sort_dir: z.enum(['desc', 'asc']).optional(),
 303 |         aux: z.string().optional()
 304 |       },
 305 |       async (params) => {
 306 |         return handleEndpoint(async () => {
 307 |           const data = await makeApiRequest(apiKey, '/v4/dex/networks/list', params)
 308 |           return formatResponse(data)
 309 |         })
 310 |       }
 311 |     )
 312 | 
 313 |     // /v4/dex/spot-pairs/latest
 314 |     server.tool("dexSpotPairsLatest",
 315 |       "Returns a paginated list of all active dex spot pairs with latest market data.",
 316 |       {
 317 |         network_id: z.string().optional(),
 318 |         network_slug: z.string().optional(),
 319 |         dex_id: z.string().optional(),
 320 |         dex_slug: z.string().optional(),
 321 |         base_asset_id: z.string().optional(),
 322 |         base_asset_symbol: z.string().optional(),
 323 |         base_asset_contract_address: z.string().optional(),
 324 |         base_asset_ucid: z.string().optional(),
 325 |         quote_asset_id: z.string().optional(),
 326 |         quote_asset_symbol: z.string().optional(),
 327 |         quote_asset_contract_address: z.string().optional(),
 328 |         quote_asset_ucid: z.string().optional(),
 329 |         scroll_id: z.string().optional(),
 330 |         limit: z.string().optional(),
 331 |         liquidity_min: z.string().optional(),
 332 |         liquidity_max: z.string().optional(),
 333 |         volume_24h_min: z.string().optional(),
 334 |         volume_24h_max: z.string().optional(),
 335 |         no_of_transactions_24h_min: z.string().optional(),
 336 |         no_of_transactions_24h_max: z.string().optional(),
 337 |         percent_change_24h_min: z.string().optional(),
 338 |         percent_change_24h_max: z.string().optional(),
 339 |         sort: z.enum(['name', 'date_added', 'price', 'volume_24h', 'percent_change_1h', 'percent_change_24h', 'liquidity', 'fully_diluted_value', 'no_of_transactions_24h']).optional(),
 340 |         sort_dir: z.enum(['desc', 'asc']).optional(),
 341 |         aux: z.string().optional(),
 342 |         reverse_order: z.string().optional(),
 343 |         convert_id: z.string().optional()
 344 |       },
 345 |       async (params) => {
 346 |         return handleEndpoint(async () => {
 347 |           const data = await makeApiRequest(apiKey, '/v4/dex/spot-pairs/latest', params)
 348 |           return formatResponse(data)
 349 |         })
 350 |       }
 351 |     )
 352 | 
 353 |     // /v4/dex/pairs/quotes/latest
 354 |     server.tool("dexPairsQuotesLatest",
 355 |       "Returns the latest market quote for 1 or more spot pairs.",
 356 |       {
 357 |         contract_address: z.string().optional(),
 358 |         network_id: z.string().optional(),
 359 |         network_slug: z.string().optional(),
 360 |         aux: z.string().optional(),
 361 |         convert_id: z.string().optional(),
 362 |         skip_invalid: z.string().optional(),
 363 |         reverse_order: z.string().optional()
 364 |       },
 365 |       async (params) => {
 366 |         return handleEndpoint(async () => {
 367 |           const data = await makeApiRequest(apiKey, '/v4/dex/pairs/quotes/latest', params)
 368 |           return formatResponse(data)
 369 |         })
 370 |       }
 371 |     )
 372 | 
 373 |     // /v4/dex/pairs/ohlcv/latest
 374 |     server.tool("dexPairsOhlcvLatest",
 375 |       "Returns the latest OHLCV market values for one or more spot pairs for the current UTC day.",
 376 |       {
 377 |         contract_address: z.string().optional(),
 378 |         network_id: z.string().optional(),
 379 |         network_slug: z.string().optional(),
 380 |         aux: z.string().optional(),
 381 |         convert_id: z.string().optional(),
 382 |         skip_invalid: z.string().optional(),
 383 |         reverse_order: z.string().optional()
 384 |       },
 385 |       async (params) => {
 386 |         return handleEndpoint(async () => {
 387 |           const data = await makeApiRequest(apiKey, '/v4/dex/pairs/ohlcv/latest', params)
 388 |           return formatResponse(data)
 389 |         })
 390 |       }
 391 |     )
 392 | 
 393 |     // /v4/dex/pairs/ohlcv/historical
 394 |     server.tool("dexPairsOhlcvHistorical",
 395 |       "Returns historical OHLCV data along with market cap for any spot pairs using time interval parameters.",
 396 |       {
 397 |         contract_address: z.string().optional(),
 398 |         network_id: z.string().optional(),
 399 |         network_slug: z.string().optional(),
 400 |         time_period: z.enum(['daily', 'hourly', '1m', '5m', '15m', '30m', '4h', '8h', '12h', 'weekly', 'monthly']).optional(),
 401 |         time_start: z.string().optional(),
 402 |         time_end: z.string().optional(),
 403 |         count: z.string().optional(),
 404 |         interval: z.enum(['1m', '5m', '15m', '30m', '1h', '4h', '8h', '12h', 'daily', 'weekly', 'monthly']).optional(),
 405 |         aux: z.string().optional(),
 406 |         convert_id: z.string().optional(),
 407 |         skip_invalid: z.string().optional(),
 408 |         reverse_order: z.string().optional()
 409 |       },
 410 |       async (params) => {
 411 |         return handleEndpoint(async () => {
 412 |           const data = await makeApiRequest(apiKey, '/v4/dex/pairs/ohlcv/historical', params)
 413 |           return formatResponse(data)
 414 |         })
 415 |       }
 416 |     )
 417 | 
 418 |     // /v4/dex/pairs/trade/latest
 419 |     server.tool("dexPairsTradeLatest",
 420 |       "Returns up to the latest 100 trades for 1 spot pair.",
 421 |       {
 422 |         contract_address: z.string().optional(),
 423 |         network_id: z.string().optional(),
 424 |         network_slug: z.string().optional(),
 425 |         aux: z.string().optional(),
 426 |         convert_id: z.string().optional(),
 427 |         skip_invalid: z.string().optional(),
 428 |         reverse_order: z.string().optional()
 429 |       },
 430 |       async (params) => {
 431 |         return handleEndpoint(async () => {
 432 |           const data = await makeApiRequest(apiKey, '/v4/dex/pairs/trade/latest', params)
 433 |           return formatResponse(data)
 434 |         })
 435 |       }
 436 |     )
 437 | 
 438 |     // /exchange/assets
 439 |     server.tool("exchangeAssets",
 440 |       "Returns the assets/token holdings of an exchange.",
 441 |       {
 442 |         id: z.string().optional(),
 443 |         slug: z.string().optional()
 444 |       },
 445 |       async (params) => {
 446 |         return handleEndpoint(async () => {
 447 |           const data = await makeApiRequest(apiKey, '/v1/exchange/assets', params)
 448 |           return formatResponse(data)
 449 |         })
 450 |       }
 451 |     )
 452 | 
 453 |     // /exchange/info
 454 |     server.tool("exchangeInfo",
 455 |       "Returns metadata for one or more exchanges.",
 456 |       {
 457 |         id: z.string().optional(),
 458 |         slug: z.string().optional(),
 459 |         aux: z.string().optional()
 460 |       },
 461 |       async (params) => {
 462 |         return handleEndpoint(async () => {
 463 |           const data = await makeApiRequest(apiKey, '/v1/exchange/info', params)
 464 |           return formatResponse(data)
 465 |         })
 466 |       }
 467 |     )
 468 | 
 469 |     // /exchange/map
 470 |     server.tool("exchangeMap",
 471 |       "Returns a mapping of all exchanges to unique CoinMarketCap IDs.",
 472 |       {
 473 |         listing_status: z.string().optional(),
 474 |         slug: z.string().optional(),
 475 |         start: z.number().optional(),
 476 |         limit: z.number().optional(),
 477 |         sort: z.string().optional()
 478 |       },
 479 |       async (params) => {
 480 |         return handleEndpoint(async () => {
 481 |           const data = await makeApiRequest(apiKey, '/v1/exchange/map', params)
 482 |           return formatResponse(data)
 483 |         })
 484 |       }
 485 |     )
 486 | 
 487 |     // /global-metrics/quotes/latest
 488 |     server.tool("globalMetricsLatest",
 489 |       "Returns the latest global cryptocurrency market metrics.",
 490 |       {
 491 |         convert: z.string().optional(),
 492 |         convert_id: z.string().optional()
 493 |       },
 494 |       async (params) => {
 495 |         return handleEndpoint(async () => {
 496 |           const data = await makeApiRequest(apiKey, '/v1/global-metrics/quotes/latest', params)
 497 |           return formatResponse(data)
 498 |         })
 499 |       }
 500 |     )
 501 | 
 502 |     // /index/quotes/historical
 503 |     server.tool("cmc100IndexHistorical",
 504 |       "Returns an interval of historic CoinMarketCap 100 Index values based on the interval parameter.",
 505 |       {
 506 |         time_start: z.string().optional(),
 507 |         time_end: z.string().optional(),
 508 |         count: z.string().optional(),
 509 |         interval: z.enum(['5m', '15m', 'daily']).optional()
 510 |       },
 511 |       async (params) => {
 512 |         return handleEndpoint(async () => {
 513 |           const data = await makeApiRequest(apiKey, '/v3/index/cmc100-historical', params)
 514 |           return formatResponse(data)
 515 |         })
 516 |       }
 517 |     )
 518 | 
 519 |     // /index/quotes/latest
 520 |     server.tool("cmc100IndexLatest",
 521 |       "Returns the lastest CoinMarketCap 100 Index value, constituents, and constituent weights.",
 522 |       {},
 523 |       async () => {
 524 |         return handleEndpoint(async () => {
 525 |           const data = await makeApiRequest(apiKey, '/v3/index/cmc100-latest')
 526 |           return formatResponse(data)
 527 |         })
 528 |       }
 529 |     )
 530 | 
 531 |     // /fear-and-greed/latest
 532 |     server.tool("fearAndGreedLatest",
 533 |       "Returns the latest CMC Crypto Fear and Greed Index value.",
 534 |       {},
 535 |       async () => {
 536 |         return handleEndpoint(async () => {
 537 |           const data = await makeApiRequest(apiKey, '/v3/fear-and-greed/latest')
 538 |           return formatResponse(data)
 539 |         })
 540 |       }
 541 |     )
 542 | 
 543 |     // /fear-and-greed/historical
 544 |     server.tool("fearAndGreedHistorical",
 545 |       "Returns historical CMC Crypto Fear and Greed Index values.",
 546 |       {
 547 |         start: z.number().min(1).optional(),
 548 |         limit: z.number().min(1).max(500).optional()
 549 |       },
 550 |       async (params) => {
 551 |         return handleEndpoint(async () => {
 552 |           const data = await makeApiRequest(apiKey, '/v3/fear-and-greed/historical', params)
 553 |           return formatResponse(data)
 554 |         })
 555 |       }
 556 |     )
 557 | 
 558 |     // /fiat/map
 559 |     server.tool("fiatMap",
 560 |       "Returns a mapping of all supported fiat currencies to unique CoinMarketCap IDs.",
 561 |       {
 562 |         start: z.number().optional(),
 563 |         limit: z.number().optional(),
 564 |         sort: z.string().optional(),
 565 |         include_metals: z.boolean().optional()
 566 |       },
 567 |       async (params) => {
 568 |         return handleEndpoint(async () => {
 569 |           const data = await makeApiRequest(apiKey, '/v1/fiat/map', params)
 570 |           return formatResponse(data)
 571 |         })
 572 |       }
 573 |     )
 574 | 
 575 |     // /tools/postman
 576 |     server.tool("getPostmanCollection",
 577 |       "Returns a Postman collection for the CoinMarketCap API.",
 578 |       {},
 579 |       async () => {
 580 |         return handleEndpoint(async () => {
 581 |           const data = await makeApiRequest(apiKey, '/v1/tools/postman')
 582 |           return formatResponse(data)
 583 |         })
 584 |       }
 585 |     )
 586 | 
 587 |     // /tools/price-conversion
 588 |     server.tool("priceConversion",
 589 |       "Convert an amount of one cryptocurrency or fiat currency into one or more different currencies.",
 590 |       {
 591 |         amount: z.number(),
 592 |         id: z.string().optional(),
 593 |         symbol: z.string().optional(),
 594 |         time: z.string().optional(),
 595 |         convert: z.string().optional(),
 596 |         convert_id: z.string().optional()
 597 |       },
 598 |       async (params) => {
 599 |         return handleEndpoint(async () => {
 600 |           const data = await makeApiRequest(apiKey, '/v2/tools/price-conversion', params)
 601 |           return formatResponse(data)
 602 |         })
 603 |       }
 604 |     )
 605 | 
 606 |     // /key/info
 607 |     server.tool("keyInfo",
 608 |       "Returns API key details and usage stats.",
 609 |       {},
 610 |       async () => {
 611 |         return handleEndpoint(async () => {
 612 |           const data = await makeApiRequest(apiKey, '/v1/key/info')
 613 |           return formatResponse(data)
 614 |         })
 615 |       }
 616 |     )
 617 |   }
 618 | 
 619 |   /*
 620 |   * HOBBYIST SUBSCRIPTION ENDPOINTS
 621 |   */
 622 |   if (checkSubscriptionLevel(subscriptionLevel, SUBSCRIPTION_LEVELS.Hobbyist)) {
 623 |     // /cryptocurrency/airdrop
 624 |     server.tool("cryptoAirdrop",
 625 |       "Returns information about a single airdrop on CoinMarketCap.",
 626 |       {
 627 |         id: z.string()
 628 |       },
 629 |       async (params) => {
 630 |         return handleEndpoint(async () => {
 631 |           const data = await makeApiRequest(apiKey, '/v1/cryptocurrency/airdrop', params)
 632 |           return formatResponse(data)
 633 |         })
 634 |       }
 635 |     )
 636 | 
 637 |     // /cryptocurrency/airdrops
 638 |     server.tool("cryptoAirdrops",
 639 |       "Returns a list of past, present, or future airdrops on CoinMarketCap.",
 640 |       {
 641 |         start: z.number().optional(),
 642 |         limit: z.number().optional(),
 643 |         status: z.string().optional(),
 644 |         id: z.string().optional(),
 645 |         slug: z.string().optional(),
 646 |         symbol: z.string().optional()
 647 |       },
 648 |       async (params) => {
 649 |         return handleEndpoint(async () => {
 650 |           const data = await makeApiRequest(apiKey, '/v1/cryptocurrency/airdrops', params)
 651 |           return formatResponse(data)
 652 |         })
 653 |       }
 654 |     )
 655 | 
 656 |     // /cryptocurrency/listings/historical
 657 |     server.tool("historicalCryptocurrencyListings",
 658 |       "Returns a ranked and sorted list of all cryptocurrencies for a historical point in time.",
 659 |       {
 660 |         timestamp: z.string().or(z.number()).optional(),
 661 |         start: z.number().optional(),
 662 |         limit: z.number().optional(),
 663 |         convert: z.string().optional(),
 664 |         convert_id: z.string().optional(),
 665 |         sort: z.string().optional(),
 666 |         sort_dir: z.string().optional(),
 667 |         cryptocurrency_type: z.string().optional(),
 668 |         aux: z.string().optional()
 669 |       },
 670 |       async (params) => {
 671 |         return handleEndpoint(async () => {
 672 |           const data = await makeApiRequest(apiKey, '/v1/cryptocurrency/listings/historical', params)
 673 |           return formatResponse(data)
 674 |         })
 675 |       }
 676 |     )
 677 | 
 678 |     // /cryptocurrency/quotes/historical
 679 |     server.tool("cryptoQuotesHistorical",
 680 |       "Returns an interval of historical market quotes for any cryptocurrency.",
 681 |       {
 682 |         id: z.string().optional(),
 683 |         slug: z.string().optional(),
 684 |         symbol: z.string().optional(),
 685 |         time_start: z.string().optional(),
 686 |         time_end: z.string().optional(),
 687 |         count: z.number().optional(),
 688 |         interval: z.string().optional(),
 689 |         convert: z.string().optional(),
 690 |         convert_id: z.string().optional(),
 691 |         aux: z.string().optional(),
 692 |         skip_invalid: z.boolean().optional()
 693 |       },
 694 |       async (params) => {
 695 |         return handleEndpoint(async () => {
 696 |           const data = await makeApiRequest(apiKey, '/v2/cryptocurrency/quotes/historical', params)
 697 |           return formatResponse(data)
 698 |         })
 699 |       }
 700 |     )
 701 | 
 702 |     // /cryptocurrency/quotes/historical/v3
 703 |     server.tool("cryptoQuotesHistoricalV3",
 704 |       "Returns an interval of historic market quotes for any cryptocurrency based on time and interval parameters.",
 705 |       {
 706 |         id: z.string().optional(),
 707 |         symbol: z.string().optional(),
 708 |         time_start: z.string().optional(),
 709 |         time_end: z.string().optional(),
 710 |         count: z.number().min(1).max(10000).optional(),
 711 |         interval: z.enum([
 712 |           '5m', '10m', '15m', '30m', '45m',
 713 |           '1h', '2h', '3h', '4h', '6h', '12h', '24h',
 714 |           '1d', '2d', '3d', '7d', '14d', '15d', '30d', '60d', '90d', '365d',
 715 |           'hourly', 'daily', 'weekly', 'monthly', 'yearly'
 716 |         ]).optional(),
 717 |         convert: z.string().optional(),
 718 |         convert_id: z.string().optional(),
 719 |         aux: z.string().optional(),
 720 |         skip_invalid: z.boolean().optional()
 721 |       },
 722 |       async (params) => {
 723 |         return handleEndpoint(async () => {
 724 |           const data = await makeApiRequest(apiKey, '/v3/cryptocurrency/quotes/historical', params)
 725 |           return formatResponse(data)
 726 |         })
 727 |       }
 728 |     )
 729 | 
 730 |     // /exchange/quotes/historical
 731 |     server.tool("exchangeQuotesHistorical",
 732 |       "Returns an interval of historic quotes for any exchange based on time and interval parameters.",
 733 |       {
 734 |         id: z.string().optional(),
 735 |         slug: z.string().optional(),
 736 |         time_start: z.string().optional(),
 737 |         time_end: z.string().optional(),
 738 |         count: z.number().min(1).max(10000).optional(),
 739 |         interval: z.enum([
 740 |           '5m', '10m', '15m', '30m', '45m',
 741 |           '1h', '2h', '3h', '4h', '6h', '12h', '24h',
 742 |           '1d', '2d', '3d', '7d', '14d', '15d', '30d', '60d', '90d', '365d',
 743 |           'hourly', 'daily', 'weekly', 'monthly', 'yearly'
 744 |         ]).optional(),
 745 |         convert: z.string().optional(),
 746 |         convert_id: z.string().optional()
 747 |       },
 748 |       async (params) => {
 749 |         return handleEndpoint(async () => {
 750 |           const data = await makeApiRequest(apiKey, '/v1/exchange/quotes/historical', params)
 751 |           return formatResponse(data)
 752 |         })
 753 |       }
 754 |     )
 755 | 
 756 |     // /global-metrics/quotes/historical
 757 |     server.tool("globalMetricsHistorical",
 758 |       "Returns historical global cryptocurrency market metrics.",
 759 |       {
 760 |         time_start: z.string().optional(),
 761 |         time_end: z.string().optional(),
 762 |         count: z.number().optional(),
 763 |         interval: z.string().optional(),
 764 |         convert: z.string().optional(),
 765 |         convert_id: z.string().optional(),
 766 |         aux: z.string().optional()
 767 |       },
 768 |       async (params) => {
 769 |         return handleEndpoint(async () => {
 770 |           const data = await makeApiRequest(apiKey, '/v1/global-metrics/quotes/historical', params)
 771 |           return formatResponse(data)
 772 |         })
 773 |       }
 774 |     )
 775 |   }
 776 | 
 777 |   /*
 778 |   * STARTUP SUBSCRIPTION ENDPOINTS
 779 |   */
 780 |   if (checkSubscriptionLevel(subscriptionLevel, SUBSCRIPTION_LEVELS.Startup)) {
 781 |     // /cryptocurrency/listings/new
 782 |     server.tool("newCryptocurrencyListings",
 783 |       "Returns a paginated list of most recently added cryptocurrencies.",
 784 |       {
 785 |         start: z.number().min(1).optional(),
 786 |         limit: z.number().min(1).max(5000).optional(),
 787 |         convert: z.string().optional(),
 788 |         convert_id: z.string().optional(),
 789 |         sort_dir: z.enum(['asc', 'desc']).optional()
 790 |       },
 791 |       async (params) => {
 792 |         return handleEndpoint(async () => {
 793 |           const data = await makeApiRequest(apiKey, '/v1/cryptocurrency/listings/new', params)
 794 |           return formatResponse(data)
 795 |         })
 796 |       }
 797 |     )
 798 | 
 799 |     // /cryptocurrency/trending/gainers-losers
 800 |     server.tool("cryptoTrendingGainersLosers",
 801 |       "Returns the biggest gainers and losers in a given time period.",
 802 |       {
 803 |         time_period: z.string().optional()
 804 |       },
 805 |       async (params) => {
 806 |         return handleEndpoint(async () => {
 807 |           const data = await makeApiRequest(apiKey, '/v1/cryptocurrency/trending/gainers-losers', params)
 808 |           return formatResponse(data)
 809 |         })
 810 |       }
 811 |     )
 812 | 
 813 |     // /cryptocurrency/trending/latest
 814 |     server.tool("cryptoTrendingLatest",
 815 |       "Returns the top cryptocurrencies by search volume in a given time period.",
 816 |       {
 817 |         time_period: z.string().optional()
 818 |       },
 819 |       async (params) => {
 820 |         return handleEndpoint(async () => {
 821 |           const data = await makeApiRequest(apiKey, '/v1/cryptocurrency/trending/latest', params)
 822 |           return formatResponse(data)
 823 |         })
 824 |       }
 825 |     )
 826 | 
 827 |     // /cryptocurrency/trending/most-visited
 828 |     server.tool("cryptoTrendingMostVisited",
 829 |       "Returns the most visited cryptocurrencies on CoinMarketCap in a given time period.",
 830 |       {
 831 |         time_period: z.string().optional()
 832 |       },
 833 |       async (params) => {
 834 |         return handleEndpoint(async () => {
 835 |           const data = await makeApiRequest(apiKey, '/v1/cryptocurrency/trending/most-visited', params)
 836 |           return formatResponse(data)
 837 |         })
 838 |       }
 839 |     )
 840 | 
 841 |     // /cryptocurrency/ohlcv/historical
 842 |     server.tool("cryptoOhlcvHistorical",
 843 |       "Returns historical OHLCV market values for one or more cryptocurrencies.",
 844 |       {
 845 |         id: z.string().optional(),
 846 |         slug: z.string().optional(),
 847 |         symbol: z.string().optional(),
 848 |         time_period: z.string().optional(),
 849 |         time_start: z.string().optional(),
 850 |         time_end: z.string().optional(),
 851 |         count: z.number().optional(),
 852 |         interval: z.string().optional(),
 853 |         convert: z.string().optional(),
 854 |         convert_id: z.string().optional(),
 855 |         skip_invalid: z.boolean().optional()
 856 |       },
 857 |       async (params) => {
 858 |         return handleEndpoint(async () => {
 859 |           const data = await makeApiRequest(apiKey, '/v2/cryptocurrency/ohlcv/historical', params)
 860 |           return formatResponse(data)
 861 |         })
 862 |       }
 863 |     )
 864 | 
 865 |     // /cryptocurrency/ohlcv/latest
 866 |     server.tool("cryptoOhlcvLatest",
 867 |       "Returns the latest OHLCV (Open, High, Low, Close, Volume) market values for one or more cryptocurrencies.",
 868 |       {
 869 |         id: z.string().optional(),
 870 |         slug: z.string().optional(),
 871 |         symbol: z.string().optional(),
 872 |         convert: z.string().optional(),
 873 |         convert_id: z.string().optional(),
 874 |         skip_invalid: z.boolean().optional()
 875 |       },
 876 |       async (params) => {
 877 |         return handleEndpoint(async () => {
 878 |           const data = await makeApiRequest(apiKey, '/v2/cryptocurrency/ohlcv/latest', params)
 879 |           return formatResponse(data)
 880 |         })
 881 |       }
 882 |     )
 883 | 
 884 |     // /cryptocurrency/price-performance-stats/latest
 885 |     server.tool("cryptoPricePerformanceStatsLatest",
 886 |       "Returns price performance statistics for one or more cryptocurrencies including ROI and ATH stats.",
 887 |       {
 888 |         id: z.string().optional(),
 889 |         slug: z.string().optional(),
 890 |         symbol: z.string().optional(),
 891 |         time_period: z.string().optional(),
 892 |         convert: z.string().optional(),
 893 |         convert_id: z.string().optional()
 894 |       },
 895 |       async (params) => {
 896 |         return handleEndpoint(async () => {
 897 |           const data = await makeApiRequest(apiKey, '/v2/cryptocurrency/price-performance-stats/latest', params)
 898 |           return formatResponse(data)
 899 |         })
 900 |       }
 901 |     )
 902 |   }
 903 | 
 904 |   /*
 905 |   * STANDARD SUBSCRIPTION ENDPOINTS
 906 |   */
 907 |   if (checkSubscriptionLevel(subscriptionLevel, SUBSCRIPTION_LEVELS.Standard)) {
 908 |     // /cryptocurrency/market-pairs/latest
 909 |     server.tool("cryptoMarketPairsLatest",
 910 |       "Returns all market pairs for the specified cryptocurrency with associated stats.",
 911 |       {
 912 |         id: z.string().optional(),
 913 |         slug: z.string().optional(),
 914 |         symbol: z.string().optional(),
 915 |         start: z.number().optional(),
 916 |         limit: z.number().optional(),
 917 |         convert: z.string().optional(),
 918 |         convert_id: z.string().optional(),
 919 |         matched_id: z.string().optional(),
 920 |         matched_symbol: z.string().optional(),
 921 |         category: z.string().optional(),
 922 |         fee_type: z.string().optional(),
 923 |         aux: z.string().optional()
 924 |       },
 925 |       async (params) => {
 926 |         return handleEndpoint(async () => {
 927 |           const data = await makeApiRequest(apiKey, '/v2/cryptocurrency/market-pairs/latest', params)
 928 |           return formatResponse(data)
 929 |         })
 930 |       }
 931 |     )
 932 | 
 933 |     // /exchange/listings/latest
 934 |     server.tool("exchangeListingsLatest",
 935 |       "Returns a paginated list of all exchanges with latest market data.",
 936 |       {
 937 |         start: z.number().optional(),
 938 |         limit: z.number().optional(),
 939 |         sort: z.string().optional(),
 940 |         sort_dir: z.string().optional(),
 941 |         convert: z.string().optional(),
 942 |         convert_id: z.string().optional(),
 943 |         aux: z.string().optional()
 944 |       },
 945 |       async (params) => {
 946 |         return handleEndpoint(async () => {
 947 |           const data = await makeApiRequest(apiKey, '/v1/exchange/listings/latest', params)
 948 |           return formatResponse(data)
 949 |         })
 950 |       }
 951 |     )
 952 | 
 953 |     // /exchange/market-pairs/latest
 954 |     server.tool("exchangeMarketPairsLatest",
 955 |       "Returns all market pairs for the specified exchange with associated stats.",
 956 |       {
 957 |         id: z.string().optional(),
 958 |         slug: z.string().optional(),
 959 |         start: z.number().optional(),
 960 |         limit: z.number().optional(),
 961 |         convert: z.string().optional(),
 962 |         convert_id: z.string().optional(),
 963 |         aux: z.string().optional()
 964 |       },
 965 |       async (params) => {
 966 |         return handleEndpoint(async () => {
 967 |           const data = await makeApiRequest(apiKey, '/v1/exchange/market-pairs/latest', params)
 968 |           return formatResponse(data)
 969 |         })
 970 |       }
 971 |     )
 972 | 
 973 |     // /exchange/quotes/latest
 974 |     server.tool("exchangeQuotesLatest",
 975 |       "Returns the latest market quotes for one or more exchanges.",
 976 |       {
 977 |         id: z.string().optional(),
 978 |         slug: z.string().optional(),
 979 |         convert: z.string().optional(),
 980 |         convert_id: z.string().optional(),
 981 |         aux: z.string().optional()
 982 |       },
 983 |       async (params) => {
 984 |         return handleEndpoint(async () => {
 985 |           const data = await makeApiRequest(apiKey, '/v1/exchange/quotes/latest', params)
 986 |           return formatResponse(data)
 987 |         })
 988 |       }
 989 |     )
 990 | 
 991 |     // /content/latest
 992 |     server.tool("contentLatest",
 993 |       "Returns latest cryptocurrency news and Alexandria articles.",
 994 |       {
 995 |         start: z.number().optional(),
 996 |         limit: z.number().optional(),
 997 |         id: z.string().optional(),
 998 |         slug: z.string().optional(),
 999 |         symbol: z.string().optional(),
1000 |         news_type: z.string().optional()
1001 |       },
1002 |       async (params) => {
1003 |         return handleEndpoint(async () => {
1004 |           const data = await makeApiRequest(apiKey, '/v1/content/latest', params)
1005 |           return formatResponse(data)
1006 |         })
1007 |       }
1008 |     )
1009 | 
1010 |     // /content/posts/top
1011 |     server.tool("contentPostsTop",
1012 |       "Returns top cryptocurrency posts.",
1013 |       {
1014 |         start: z.number().optional(),
1015 |         limit: z.number().optional()
1016 |       },
1017 |       async (params) => {
1018 |         return handleEndpoint(async () => {
1019 |           const data = await makeApiRequest(apiKey, '/v1/content/posts/top', params)
1020 |           return formatResponse(data)
1021 |         })
1022 |       }
1023 |     )
1024 | 
1025 |     // /content/posts/latest
1026 |     server.tool("contentPostsLatest",
1027 |       "Returns latest cryptocurrency posts.",
1028 |       {
1029 |         start: z.number().optional(),
1030 |         limit: z.number().optional()
1031 |       },
1032 |       async (params) => {
1033 |         return handleEndpoint(async () => {
1034 |           const data = await makeApiRequest(apiKey, '/v1/content/posts/latest', params)
1035 |           return formatResponse(data)
1036 |         })
1037 |       }
1038 |     )
1039 | 
1040 |     // /content/posts/comments
1041 |     server.tool("contentPostsComments",
1042 |       "Returns comments for a specific post.",
1043 |       {
1044 |         id: z.string(),
1045 |         start: z.number().optional(),
1046 |         limit: z.number().optional()
1047 |       },
1048 |       async (params) => {
1049 |         return handleEndpoint(async () => {
1050 |           const data = await makeApiRequest(apiKey, '/v1/content/posts/comments', params)
1051 |           return formatResponse(data)
1052 |         })
1053 |       }
1054 |     )
1055 | 
1056 |     // /community/trending/topic
1057 |     server.tool("communityTrendingTopic",
1058 |       "Returns community trending topics.",
1059 |       {
1060 |         start: z.number().optional(),
1061 |         limit: z.number().optional()
1062 |       },
1063 |       async (params) => {
1064 |         return handleEndpoint(async () => {
1065 |           const data = await makeApiRequest(apiKey, '/v1/community/trending/topic', params)
1066 |           return formatResponse(data)
1067 |         })
1068 |       }
1069 |     )
1070 | 
1071 |     // /community/trending/token
1072 |     server.tool("communityTrendingToken",
1073 |       "Returns community trending tokens.",
1074 |       {
1075 |         start: z.number().optional(),
1076 |         limit: z.number().optional()
1077 |       },
1078 |       async (params) => {
1079 |         return handleEndpoint(async () => {
1080 |           const data = await makeApiRequest(apiKey, '/v1/community/trending/token', params)
1081 |           return formatResponse(data)
1082 |         })
1083 |       }
1084 |     )
1085 |   }
1086 | 
1087 |   /*
1088 |   * ENTERPRISE SUBSCRIPTION ENDPOINTS
1089 |   */
1090 |   if (checkSubscriptionLevel(subscriptionLevel, SUBSCRIPTION_LEVELS.Enterprise)) {
1091 |     // /blockchain/statistics/latest
1092 |     server.tool("blockchainStatisticsLatest",
1093 |       "Returns the latest statistics for one or more blockchains.",
1094 |       {
1095 |         id: z.string().optional(),
1096 |         slug: z.string().optional(),
1097 |         symbol: z.string().optional()
1098 |       },
1099 |       async (params) => {
1100 |         return handleEndpoint(async () => {
1101 |           const data = await makeApiRequest(apiKey, '/v1/blockchain/statistics/latest', params)
1102 |           return formatResponse(data)
1103 |         })
1104 |       }
1105 |     )
1106 |   }
1107 | 
1108 |   return server
1109 | }
1110 | 
1111 | // Stdio Server 
1112 | const stdioServer = createServer({})
1113 | const transport = new StdioServerTransport()
1114 | await stdioServer.connect(transport)
1115 | 
1116 | // Streamable HTTP Server
1117 | const { app } = createStatefulServer(createServer)
1118 | const PORT = process.env.PORT || 3000
1119 | app.listen(PORT)
1120 | 
```