mcp-crypto-toolkit 1.2.7 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.0] - 2026-09-04
11
+
12
+ ### Added
13
+
14
+ - Cloudflare Workers deploy (`src/worker.ts`, `wrangler.toml`, `npm run deploy:cf`)
15
+ - Hosted remote MCP: `https://mcp-crypto-toolkit.nad33mahm3d.workers.dev/mcp`
16
+ - `trending` tool — CoinGecko trending coins + Fear & Greed Index
17
+ - `whats-trending` prompt; market-overview also calls trending
18
+ - Worker per-IP rate limit (default 60 req/min) and optional `MCP_API_KEY` gate
19
+ - GitHub Action to deploy Worker on release (`deploy-cf.yml`)
20
+ - README: live connector URL, DeepWiki, mcpservers.org badge, Claude connector screenshot
21
+ - `server.json` `remotes` entry for the hosted endpoint
22
+
10
23
  ## [1.2.7] - 2026-09-04
11
24
 
12
25
  ### Fixed
package/README.md CHANGED
@@ -4,13 +4,20 @@
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
  [![MCP](https://img.shields.io/badge/MCP-Server-blue.svg)](https://modelcontextprotocol.io)
6
6
  [![CI](https://github.com/nad33mahm3d/mcp-crypto-toolkit/actions/workflows/ci.yml/badge.svg)](https://github.com/nad33mahm3d/mcp-crypto-toolkit/actions/workflows/ci.yml)
7
+ [![Listed on mcpservers.org](https://mcpservers.org/badge.svg)](https://mcpservers.org/servers/nad33mahm3d/mcp-crypto-toolkit)
8
+ [![DeepWiki](https://img.shields.io/badge/DeepWiki-docs-blueviolet)](https://deepwiki.com/nad33mahm3d/mcp-crypto-toolkit)
7
9
 
8
10
  **Live crypto prices, conversion, gas tracker, portfolio tools, and calculators for AI agents.**
9
11
 
10
12
  Ask Claude: *What's ETH doing? Top 10 coins? Gas on Base? What's my portfolio worth?*
11
13
 
14
+ **Hosted (Claude.ai Custom Connector):** [`https://mcp-crypto-toolkit.nad33mahm3d.workers.dev/mcp`](https://mcp-crypto-toolkit.nad33mahm3d.workers.dev/mcp)
15
+ **Docs:** [DeepWiki](https://deepwiki.com/nad33mahm3d/mcp-crypto-toolkit) · [mcpservers.org](https://mcpservers.org/servers/nad33mahm3d/mcp-crypto-toolkit)
16
+
12
17
  ![Demo](https://raw.githubusercontent.com/nad33mahm3d/mcp-crypto-toolkit/main/demo.gif)
13
18
 
19
+ ![Claude.ai Custom Connector — 10 tools](https://raw.githubusercontent.com/nad33mahm3d/mcp-crypto-toolkit/main/docs/claude-connector.png)
20
+
14
21
  ## Features
15
22
 
16
23
  - Live prices with market cap, volume, rank, ATH/ATL (CoinGecko)
@@ -22,9 +29,10 @@ Ask Claude: *What's ETH doing? Top 10 coins? Gas on Base? What's my portfolio wo
22
29
  - Historical point-in-time + range charts (7d/30d/…)
23
30
  - EVM gas tracker with USD transfer estimates
24
31
  - Profit/loss calculator with exchange + network fees
32
+ - Trending coins + Crypto Fear & Greed Index
25
33
  - MCP prompts for common workflows
26
34
  - **Zero config** — no API key required (optional `COINGECKO_API_KEY` for higher limits)
27
- - stdio + HTTP transports
35
+ - stdio + HTTP + Cloudflare Workers transports
28
36
 
29
37
  ## Quick Install
30
38
 
@@ -76,10 +84,17 @@ Only deploy HTTP if you want a **remote** `/mcp` endpoint (e.g. Claude.ai Custom
76
84
 
77
85
  #### Claude.ai Custom Connector
78
86
 
79
- 1. Deploy HTTP (`npm run start:http` or Docker / MCP Hosting / Render)
80
- 2. In Claude.ai: **Settings → Connectors → Add custom connector**
81
- 3. URL: `https://YOUR-HOST/mcp` (must include `/mcp`)
82
- 4. After upgrading the server, remove and re-add the connector if tools still show as empty
87
+ Public hosted endpoint (Cloudflare Workers):
88
+
89
+ ```
90
+ https://mcp-crypto-toolkit.nad33mahm3d.workers.dev/mcp
91
+ ```
92
+
93
+ 1. In Claude.ai: **Settings → Connectors → Add custom connector**
94
+ 2. Paste the URL above (must include `/mcp`)
95
+ 3. You should see **10 tools** (get price, convert, trending, …)
96
+
97
+ Or deploy your own (Cloudflare / Docker / Render) and use `https://YOUR-HOST/mcp`. After upgrading a hosted server, remove and re-add the connector if tools still show as empty.
83
98
 
84
99
  Do **not** put a remote URL in `claude_desktop_config.json` — that file is stdio-only. For Claude Desktop local use, prefer:
85
100
 
@@ -94,6 +109,34 @@ Do **not** put a remote URL in `claude_desktop_config.json` — that file is std
94
109
  }
95
110
  ```
96
111
 
112
+ **Cloudflare Workers (recommended remote — free tier)**
113
+
114
+ Uses the web-standard MCP transport (`src/worker.ts`) — no Docker required.
115
+
116
+ Live demo: [health](https://mcp-crypto-toolkit.nad33mahm3d.workers.dev/health) · [MCP](https://mcp-crypto-toolkit.nad33mahm3d.workers.dev/mcp)
117
+
118
+ ```bash
119
+ npm install
120
+ npx wrangler login
121
+ npm run deploy:cf
122
+ ```
123
+
124
+ After deploy: `https://mcp-crypto-toolkit.<your-subdomain>.workers.dev/health`
125
+ Claude / Smithery URL: `https://mcp-crypto-toolkit.<your-subdomain>.workers.dev/mcp`
126
+
127
+ Local preview: `npm run dev:cf`
128
+
129
+ Optional secrets / vars:
130
+
131
+ ```bash
132
+ npx wrangler secret put COINGECKO_API_KEY
133
+ npx wrangler secret put MCP_API_KEY # optional: require Bearer / x-api-key on /mcp
134
+ # Pro keys also need: wrangler.toml [vars] COINGECKO_PRO = "1"
135
+ # Optional: RATE_LIMIT_PER_MINUTE = "60" (default)
136
+ ```
137
+
138
+ CI: add repo secrets `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` — Worker auto-deploys on GitHub Release (and via workflow_dispatch).
139
+
97
140
  **Render (free — no credit card)**
98
141
 
99
142
  1. Fork/connect repo at [render.com](https://render.com)
@@ -114,7 +157,7 @@ docker run -p 3000:3000 mcp-crypto-toolkit
114
157
 
115
158
  Optional env: `COINGECKO_API_KEY`, `COINGECKO_PRO=1`, gas oracle keys (see table below).
116
159
 
117
- ## Tools (9)
160
+ ## Tools (10)
118
161
 
119
162
  | Tool | Description |
120
163
  |------|-------------|
@@ -127,12 +170,14 @@ Optional env: `COINGECKO_API_KEY`, `COINGECKO_PRO=1`, gas oracle keys (see table
127
170
  | `historical_price` | Point date or range chart + investment snapshot |
128
171
  | `gas_tracker` | Gas fees + estimated transfer cost (USD) |
129
172
  | `profit_calc` | P&L, ROI, break-even (fees + network fee) |
173
+ | `trending` | Trending coins + Fear & Greed Index |
130
174
 
131
175
  ## Prompts
132
176
 
133
177
  | Prompt | Purpose |
134
178
  |--------|---------|
135
- | `market-overview` | Top coins + market tone |
179
+ | `market-overview` | Top coins + trending + market tone |
180
+ | `whats-trending` | Trending coins + Fear & Greed |
136
181
  | `analyze-coin` | Price + 7d trend for one coin |
137
182
  | `trade-pnl` | Walk through a P&L calc |
138
183
  | `gas-check` | Gas + transfer cost advice |
@@ -148,6 +193,7 @@ Optional env: `COINGECKO_API_KEY`, `COINGECKO_PRO=1`, gas oracle keys (see table
148
193
  7. *Show SOL's 30-day price range*
149
194
  8. *What's gas on Base right now in USD?*
150
195
  9. *I bought at 100, sold at 120, qty 10, 0.1% fees — profit?*
196
+ 10. *What's trending and what's the Fear & Greed index?*
151
197
 
152
198
  ## Development
153
199
 
@@ -166,6 +212,8 @@ npm run inspector
166
212
  |----------|---------|
167
213
  | `COINGECKO_API_KEY` | Higher CoinGecko rate limits (still optional) |
168
214
  | `COINGECKO_PRO=1` | Use Pro header with your key |
215
+ | `MCP_API_KEY` | Worker only: require Bearer / `x-api-key` on `/mcp` |
216
+ | `RATE_LIMIT_PER_MINUTE` | Worker only: default `60` |
169
217
  | `ETHERSCAN_API_KEY` / `BSCSCAN_API_KEY` / … | Better gas oracles |
170
218
  | `OPTIMISM_API_KEY` | Optimism gas oracle |
171
219
  | `PORT` | HTTP server port (default 3000) |
@@ -180,6 +228,8 @@ See [CONTRIBUTING.md](https://github.com/nad33mahm3d/mcp-crypto-toolkit/blob/mai
180
228
  - [Security Policy](https://github.com/nad33mahm3d/mcp-crypto-toolkit/blob/main/SECURITY.md)
181
229
  - [Support](https://github.com/nad33mahm3d/mcp-crypto-toolkit/blob/main/SUPPORT.md)
182
230
  - [Changelog](https://github.com/nad33mahm3d/mcp-crypto-toolkit/blob/main/CHANGELOG.md)
231
+ - [DeepWiki](https://deepwiki.com/nad33mahm3d/mcp-crypto-toolkit) — auto-generated architecture docs
232
+ - [mcpservers.org](https://mcpservers.org/servers/nad33mahm3d/mcp-crypto-toolkit)
183
233
 
184
234
  ### Releasing (maintainers)
185
235
 
@@ -190,7 +240,8 @@ See [CONTRIBUTING.md](https://github.com/nad33mahm3d/mcp-crypto-toolkit/blob/mai
190
240
 
191
241
  ## API Credits
192
242
 
193
- - [CoinGecko](https://www.coingecko.com/) — prices & markets (free tier, no key required)
243
+ - [CoinGecko](https://www.coingecko.com/) — prices, markets, trending (free tier, no key required)
244
+ - [Alternative.me](https://alternative.me/crypto/fear-and-greed-index/) — Fear & Greed Index
194
245
  - [Binance P2P](https://p2p.binance.com/) — USDT/PKR when converting to PKR
195
246
  - [Etherscan](https://etherscan.io/) family — optional gas oracles
196
247
  - [Blocknative](https://www.blocknative.com/) — gas fallback
package/dist/http.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{createServer as We}from"http";import{readFileSync as Qe}from"fs";import{fileURLToPath as Xe}from"url";import{dirname as Ze,join as et}from"path";import{Server as tt}from"@modelcontextprotocol/sdk/server/index.js";import{StreamableHTTPServerTransport as Ie}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{CallToolRequestSchema as He,ListToolsRequestSchema as Ke,ListPromptsRequestSchema as Ye,GetPromptRequestSchema as Be}from"@modelcontextprotocol/sdk/types.js";var X={btc:"bitcoin",bitcoin:"bitcoin",eth:"ethereum",ethereum:"ethereum",usdt:"tether",tether:"tether",bnb:"binancecoin",binancecoin:"binancecoin",sol:"solana",solana:"solana",xrp:"ripple",ripple:"ripple",usdc:"usd-coin","usd-coin":"usd-coin",ada:"cardano",cardano:"cardano",avax:"avalanche-2",avalanche:"avalanche-2",doge:"dogecoin",dogecoin:"dogecoin",dot:"polkadot",polkadot:"polkadot",trx:"tron",tron:"tron",link:"chainlink",chainlink:"chainlink",matic:"matic-network",polygon:"matic-network","matic-network":"matic-network",shib:"shiba-inu","shiba-inu":"shiba-inu",ltc:"litecoin",litecoin:"litecoin",bch:"bitcoin-cash","bitcoin-cash":"bitcoin-cash",uni:"uniswap",uniswap:"uniswap",atom:"cosmos",cosmos:"cosmos",xlm:"stellar",stellar:"stellar",xmr:"monero",monero:"monero",etc:"ethereum-classic","ethereum-classic":"ethereum-classic",fil:"filecoin",filecoin:"filecoin",apt:"aptos",aptos:"aptos",arb:"arbitrum",arbitrum:"arbitrum",op:"optimism",optimism:"optimism",near:"near",vet:"vechain",vechain:"vechain",icp:"internet-computer",hbar:"hedera-hashgraph",algo:"algorand",algorand:"algorand",qnt:"quant-network",eos:"eos",aave:"aave",grt:"the-graph",sand:"the-sandbox",mana:"decentraland",axs:"axie-infinity",theta:"theta-token",ftm:"fantom",fantom:"fantom",xtz:"tezos",tezos:"tezos",rune:"thorchain",snx:"havven",crv:"curve-dao-token",mkr:"maker",maker:"maker",comp:"compound-governance-token",ldo:"lido-dao",sui:"sui",sei:"sei-network",inj:"injective-protocol",tia:"celestia",pepe:"pepe",wif:"dogwifcoin",bonk:"bonk",floki:"floki",render:"render-token",rndr:"render-token",fet:"fetch-ai",imx:"immutable-x",gala:"gala",enj:"enjincoin",bat:"basic-attention-token",zec:"zcash",dash:"dash",neo:"neo",kcs:"kucoin-shares",okb:"okb",ton:"the-open-network",kas:"kaspa",stx:"blockstack",rpl:"rocket-pool",blur:"blur",pendle:"pendle",jup:"jupiter-exchange-solana",pyth:"pyth-network",wld:"worldcoin-wld",strk:"starknet",ethfi:"ether-fi",ena:"ethena",ondo:"ondo-finance",not:"notcoin",bome:"book-of-meme",ar:"arweave",flow:"flow",mina:"mina-protocol",ro:"ronin",ronin:"ronin",egld:"elrond-erd-2",egld_m:"elrond-erd-2",cake:"pancakeswap-token","1inch":"1inch",ens:"ethereum-name-service",chz:"chiliz",hot:"holotoken",zil:"zilliqa",icx:"icon",waves:"waves",kava:"kava",celo:"celo",rose:"oasis-network",one:"harmony",iota:"iota",xem:"nem",sc:"siacoin",dcr:"decred",rvn:"ravencoin",zen:"zencash",ont:"ontology",qtum:"qtum",btt:"bittorrent",hnt:"helium",lrc:"loopring",ankr:"ankr",omg:"omisego",storj:"storj",skl:"skale",celr:"celer-network",coti:"coti",ren:"republic-protocol",band:"band-protocol",ocean:"ocean-protocol",api3:"api3",nmr:"numeraire",bal:"balancer",yfi:"yearn-finance",sushi:"sushi",dydx:"dydx-chain",gmx:"gmx",magic:"magic",rdnt:"radiant-capital",joe:"joe",woo:"woo-network",perp:"perpetual-protocol",lqty:"liquity",cvx:"convex-finance",frax:"frax",fxn:"fxn",usdd:"usdd",tusd:"true-usd",dai:"dai",frax_usd:"frax"};var Z={eth:1,bnb:56,polygon:137,arbitrum:42161,base:8453,optimism:10},ee={eth:"ethereum",bnb:"binancecoin",polygon:"matic-network",arbitrum:"ethereum",base:"ethereum",optimism:"ethereum"},K=21e3,te={eth:{url:"https://api.etherscan.io/api",apiKeyEnv:"ETHERSCAN_API_KEY"},bnb:{url:"https://api.bscscan.com/api",apiKeyEnv:"BSCSCAN_API_KEY"},polygon:{url:"https://api.polygonscan.com/api",apiKeyEnv:"POLYGONSCAN_API_KEY"},arbitrum:{url:"https://api.arbiscan.io/api",apiKeyEnv:"ARBISCAN_API_KEY"},base:{url:"https://api.basescan.org/api",apiKeyEnv:"BASESCAN_API_KEY"},optimism:{url:"https://api-optimistic.etherscan.io/api",apiKeyEnv:"OPTIMISM_API_KEY"}},R="mcp-crypto-toolkit/1.0.0 (https://github.com/nad33mahm3d/mcp-crypto-toolkit)";var T=class{store=new Map;timers=new Map;get(e){let o=this.store.get(e);if(o){if(Date.now()>o.expiresAt){this.delete(e);return}return o.value}}set(e,o,a){let n=this.timers.get(e);n&&clearTimeout(n),this.store.set(e,{value:o,expiresAt:Date.now()+a});let r=setTimeout(()=>{this.delete(e)},a);r.unref?.(),this.timers.set(e,r)}clear(){for(let e of this.timers.values())clearTimeout(e);this.timers.clear(),this.store.clear()}delete(e){let o=this.timers.get(e);o&&(clearTimeout(o),this.timers.delete(e)),this.store.delete(e)}},I=new T,Y=new T,G=new T,F=new T,k={PRICE:6e4,GAS:15e3,HISTORICAL:36e5,PKR:3e5,SEARCH:12e4};var O="https://api.coingecko.com/api/v3",y=class extends Error{constructor(o,a,n=!1){super(o);this.status=a;this.retryable=n;this.name="CoinGeckoError"}status;retryable};function f(t){let e=t.toLowerCase().trim();return X[e]??e}function $e(){let t=process.env.COINGECKO_API_KEY?.trim();return t?t.startsWith("CG-")&&process.env.COINGECKO_PRO==="1"?{"x-cg-pro-api-key":t}:{"x-cg-demo-api-key":t}:{}}async function j(t,e={},o=3){let a;for(let n=0;n<o;n++)try{let r=await fetch(t,{...e,headers:{Accept:"application/json","User-Agent":R,...$e(),...e.headers??{}}});if(r.status===429){let i=Math.min(1e3*2**n,1e4);if(n<o-1){await re(i);continue}throw new y("CoinGecko rate limit exceeded (429). Wait a few seconds, or set COINGECKO_API_KEY for higher limits.",429,!0)}if(!r.ok)throw new y(`CoinGecko API error: ${r.status} ${r.statusText}`,r.status,r.status>=500);return r}catch(r){if(r instanceof y)throw r;a=r instanceof Error?r:new Error(String(r)),n<o-1&&await re(1e3*2**n)}throw a??new Error("Fetch failed after retries")}async function P(t,e){let o=[...new Set(t.map(f))],a=[...new Set(e.map(p=>p.toLowerCase()))],n=`simple:${o.sort().join(",")}:${a.sort().join(",")}`,r=I.get(n);if(r)return r;let i=new URLSearchParams({ids:o.join(","),vs_currencies:a.join(","),include_24hr_change:"true",include_last_updated_at:"true"}),u=await(await j(`${O}/simple/price?${i}`)).json();return I.set(n,u,k.PRICE),u}async function S(t,e){let o=f(t),a=e.toLowerCase(),r=(await P([o],[a]))[o];if(!r)throw new y(`Coin not found: ${t} (id: ${o})`);let i=r[a];if(i===void 0)throw new y(`Currency not supported: ${e}`);let c=`${a}_24h_change`,u="last_updated_at";return{id:o,price:Number(i),change24h:r[c]!==void 0?Number(r[c]):void 0,lastUpdated:r[u]!==void 0?new Date(Number(r[u])*1e3).toISOString():new Date().toISOString()}}async function q(t,e={}){let o=t.toLowerCase(),a=e.perPage??10,n=e.page??1,r=e.order??"market_cap_desc",i=e.ids?.map(f),c=`markets:${o}:${i?.sort().join(",")??"all"}:${a}:${n}:${r}`,u=I.get(c);if(u)return u;let p=new URLSearchParams({vs_currency:o,order:r,per_page:String(a),page:String(n),sparkline:"false",price_change_percentage:"24h"});i?.length&&p.set("ids",i.join(","));let l=await(await j(`${O}/coins/markets?${p}`)).json();return I.set(c,l,k.PRICE),l}async function B(t,e){let o=f(t),n=(await q(e,{ids:[o],perPage:1}))[0];if(!n)throw new y(`Coin not found: ${t} (id: ${o})`);return n}async function ne(t,e=10){let o=t.trim();if(!o)return[];let a=`search:${o.toLowerCase()}:${e}`,n=I.get(a);if(n)return n;let c=((await(await j(`${O}/search?query=${encodeURIComponent(o)}`)).json()).coins??[]).slice(0,e).map(u=>({id:u.id,name:u.name,symbol:u.symbol,market_cap_rank:u.market_cap_rank}));return I.set(a,c,k.PRICE),c}async function oe(t,e,o){let a=f(t),n=o.toLowerCase(),r=`hist:${a}:${e}:${n}`,i=G.get(r);if(i)return i;let u=await(await j(`${O}/coins/${a}/history?date=${encodeURIComponent(e)}&localization=false`)).json();if(u.error||!u.market_data?.current_price?.[n])throw new y(u.error??`Historical price not found for ${t} on ${e}`);let p={id:a,date:e,price:u.market_data.current_price[n],vsCurrency:n};return G.set(r,p,k.HISTORICAL),p}async function ae(t,e,o){let a=f(t),n=e.toLowerCase(),r=String(o),i=`chart:${a}:${n}:${r}`,c=G.get(i);if(c)return c;let u=new URLSearchParams({vs_currency:n,days:r}),l=((await(await j(`${O}/coins/${a}/market_chart?${u}`)).json()).prices??[]).map(([v,h])=>({timestamp:v,price:h})),d={id:a,vs:n,days:r,prices:l};return G.set(i,d,k.HISTORICAL),d}async function se(t,e){return(await S(t,e)).price}function re(t){return new Promise(e=>setTimeout(e,t))}function s(t,e=2){return Number.isFinite(t)?Number(t.toFixed(e)):0}function _(t,e=2){return Number.isFinite(t)?Number(t.toFixed(e)):0}function g(t,e){return t==="btc"||t==="eth"?8:e>=1?2:e>=.01?4:8}import{z as V}from"zod";async function L(){let t=F.get("usdt-pkr");if(t)return t;try{let o={rate:await Ne(),source:"binance_p2p",lastUpdated:new Date().toISOString()};return F.set("usdt-pkr",o,k.PKR),o}catch{let o={rate:await Ge(),source:"coingecko",lastUpdated:new Date().toISOString()};return F.set("usdt-pkr",o,k.PKR),o}}async function Ne(){let t=await fetch("https://p2p.binance.com/bapi/c2c/v2/friendly/c2c/adv/search",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json","User-Agent":R},body:JSON.stringify({asset:"USDT",fiat:"PKR",merchantCheck:!1,page:1,payTypes:[],publisherType:null,rows:10,tradeType:"BUY"})});if(!t.ok)throw new Error(`Binance P2P API error: ${t.status}`);let o=(await t.json()).data?.slice(0,5)??[];if(o.length===0)throw new Error("No Binance P2P ads found for USDT/PKR");let a=o.map(r=>parseFloat(r.adv.price)).filter(r=>!isNaN(r));if(a.length===0)throw new Error("Invalid Binance P2P price data");let n=a.reduce((r,i)=>r+i,0)/a.length;return Number(n.toFixed(2))}async function Ge(){let e=(await P(["tether"],["pkr"])).tether?.pkr;if(e===void 0)throw new y("Could not fetch USDT/PKR rate from CoinGecko");return Number(e.toFixed(2))}async function D(t,e){let o=e.toLowerCase();if(o==="pkr")return{converted:t,rate:1,source:"direct"};if(o==="usdt"||o==="tether"){let u=await L();return{converted:Number((t*u.rate).toFixed(2)),rate:u.rate,source:u.source}}let a=f(o),r=(await P([a],["usd"]))[a]?.usd;if(r===void 0)throw new y(`Could not get USD price for ${e}`);let i=t*r,c=await L();return{converted:Number((i*c.rate).toFixed(2)),rate:Number((r*c.rate).toFixed(2)),source:`coingecko_usd + ${c.source}`}}async function ie(t,e){let o=e.toLowerCase();if(o==="pkr")return{converted:t,rate:1,source:"direct"};let a=await L(),n=t/a.rate;if(o==="usdt"||o==="tether"||o==="usd")return{converted:Number(n.toFixed(6)),rate:Number((1/a.rate).toFixed(8)),source:a.source};let r=f(o),c=(await P([r],["usd"]))[r]?.usd;if(!c)throw new y(`Could not get USD price for ${e}`);let u=n/c;return{converted:Number(u.toFixed(8)),rate:Number((a.rate/c).toFixed(2)),source:`coingecko_usd + ${a.source}`}}var ce=V.object({coin:V.string().describe("Coin id or symbol: btc, bitcoin, eth, sol, etc"),vs_currency:V.string().default("usd").describe("Fiat: usd, eur, pkr, inr, gbp, etc")});async function ue(t){let{coin:e,vs_currency:o}=t,a=o.toLowerCase();if(a==="pkr"){let n=await L(),r=await B(e,"usd"),i=(r.current_price??0)*n.rate;return{coin:r.id,symbol:r.symbol,name:r.name,price:s(i,2),vs_currency:"pkr","24h_change":r.price_change_percentage_24h?_(r.price_change_percentage_24h):null,market_cap:r.market_cap?s(r.market_cap*n.rate,0):null,market_cap_rank:r.market_cap_rank,total_volume:r.total_volume?s(r.total_volume*n.rate,0):null,high_24h:r.high_24h?s(r.high_24h*n.rate,2):null,low_24h:r.low_24h?s(r.low_24h*n.rate,2):null,ath:r.ath?s(r.ath*n.rate,2):null,ath_change_percent:r.ath_change_percentage?_(r.ath_change_percentage):null,usd_price:s(r.current_price,2),rate_source:n.source,last_updated:r.last_updated??new Date().toISOString()}}try{let n=await B(e,a),r=n.current_price??0;return{coin:n.id,symbol:n.symbol,name:n.name,price:s(r,g(a,r)),vs_currency:a,"24h_change":n.price_change_percentage_24h?_(n.price_change_percentage_24h):null,market_cap:n.market_cap?s(n.market_cap,0):null,market_cap_rank:n.market_cap_rank,total_volume:n.total_volume?s(n.total_volume,0):null,high_24h:n.high_24h?s(n.high_24h,g(a,n.high_24h)):null,low_24h:n.low_24h?s(n.low_24h,g(a,n.low_24h)):null,ath:n.ath?s(n.ath,g(a,n.ath)):null,ath_change_percent:n.ath_change_percentage?_(n.ath_change_percentage):null,atl:n.atl?s(n.atl,g(a,n.atl)):null,last_updated:n.last_updated??new Date().toISOString()}}catch{let n=await S(e,a);return{coin:n.id,price:s(n.price,g(a,n.price)),vs_currency:a,"24h_change":n.change24h?_(n.change24h):null,last_updated:n.lastUpdated??new Date().toISOString()}}}import{z}from"zod";var pe=z.object({amount:z.number(),from:z.string(),to:z.string().describe("usd, pkr, btc, eth, etc")});async function le(t){let{amount:e,from:o,to:a}=t,n=o.toLowerCase(),r=a.toLowerCase();if(n===r)return{amount:e,from:n,to:r,converted:e,rate:1,source:"direct"};if(r==="pkr"){let h=await D(e,n);return{amount:e,from:f(n),to:"pkr",converted:h.converted,rate:h.rate,source:h.source}}if(n==="pkr"){let h=await ie(e,r);return{amount:e,from:"pkr",to:f(r),converted:h.converted,rate:h.rate,source:h.source}}let i=f(n),c=f(r),u=new Set(["usd","eur","gbp","jpy","inr","aud","cad","chf","cny"]);if(u.has(r)){let h=await S(n,r);return{amount:e,from:i,to:r,converted:s(e*h.price,2),rate:s(h.price,2),source:"coingecko"}}if(u.has(n)){let h=await S(r,n);return{amount:e,from:n,to:c,converted:s(e/h.price,8),rate:s(1/h.price,8),source:"coingecko"}}let p=await P([i,c],["usd"]),m=p[i]?.usd,l=p[c]?.usd;if(!m||!l)throw new Error(`Could not convert ${o} to ${a}`);let d=m/l,v=e*d;return{amount:e,from:i,to:c,converted:s(v,8),rate:s(d,8),source:"coingecko"}}import{z as me}from"zod";var Oe=["eth","bnb","polygon","arbitrum","base","optimism"],de=me.object({chain:me.enum(Oe).default("eth")});async function he(t){let e=t.chain,o=`gas:${e}`,a=Y.get(o);if(a)return a;let n,r=te[e],i=r?process.env[r.apiKeyEnv]:void 0;if(r&&i)try{n=await qe(e,r.url,i)}catch{}n||(n=await Le(e));let c=await je(n);return Y.set(o,c,k.GAS),c}function J(t){return t*K/1e9}async function je(t){let e=ee[t.chain]??"ethereum",o=null;try{o=(await S(e,"usd")).price}catch{o=null}let a=J(t.low),n=J(t.average),r=J(t.high);return{...t,estimated_transfer:{gas_units:K,cost_native_low:s(a,8),cost_native_average:s(n,8),cost_native_high:s(r,8),cost_usd_low:o!==null?s(a*o,4):null,cost_usd_average:o!==null?s(n*o,4):null,cost_usd_high:o!==null?s(r*o,4):null,native_token:e}}}async function qe(t,e,o){let a=`${e}?module=gastracker&action=gasoracle&apikey=${o}`,n=await fetch(a,{headers:{"User-Agent":R,Accept:"application/json"}});if(!n.ok)throw new Error(`Scan API error: ${n.status}`);let r=await n.json();if(r.status!=="1"||!r.result)throw new Error("Invalid scan gas oracle response");return{chain:t,low:s(parseFloat(r.result.SafeGasPrice??"0"),2),average:s(parseFloat(r.result.ProposeGasPrice??"0"),2),high:s(parseFloat(r.result.FastGasPrice??"0"),2),baseFee:r.result.suggestBaseFee?s(parseFloat(r.result.suggestBaseFee),2):null,unit:"gwei",last_updated:new Date().toISOString(),source:`${t}scan`}}async function Le(t){let e=Z[t];if(!e)throw new Error(`Unsupported chain: ${t}`);try{let o=await fetch(`https://api.blocknative.com/gasprices/blockprices?chainid=${e}`,{headers:{Accept:"application/json","User-Agent":R}});if(o.ok){let n=(await o.json()).blockPrices?.[0],r=n?.estimatedPrices??[];if(r.length>0){let i=[...r].sort((l,d)=>l.price-d.price),c=i[0]?.price??0,u=i[i.length-1]?.price??0,p=i[Math.floor(i.length/2)]?.price??c,m=n?.baseFeePerGas?n.baseFeePerGas/1e9:null;return{chain:t,low:s(c,2),average:s(p,2),high:s(u,2),baseFee:m!==null?s(m,2):null,unit:"gwei",last_updated:new Date().toISOString(),source:"blocknative"}}}}catch{}return Me(t)}function Me(t){let e={eth:{low:15,average:25,high:40},bnb:{low:3,average:5,high:8},polygon:{low:30,average:50,high:80},arbitrum:{low:.1,average:.2,high:.5},base:{low:.01,average:.05,high:.1},optimism:{low:.01,average:.05,high:.1}},o=e[t]??e.eth;return{chain:t,low:o.low,average:o.average,high:o.high,baseFee:null,unit:"gwei",last_updated:new Date().toISOString(),source:"static_estimate"}}import{z as A}from"zod";var ge=A.object({buy_price:A.number(),sell_price:A.number(),quantity:A.number(),buy_fee_percent:A.number().default(0),sell_fee_percent:A.number().default(0),network_fee:A.number().default(0).describe("Fixed network/gas fee in the same unit as prices (e.g. USD)")});function fe(t){let e=t.buy_price,o=t.sell_price,a=t.quantity,n=t.buy_fee_percent??0,r=t.sell_fee_percent??0,i=t.network_fee??0,c=1+n/100,u=1-r/100,p=e*a*c,m=o*a*u,l=m-p,d=l-i,v=p+i,h=v>0?d/v*100:0,M=u>0?e*c/u+i/(a*u||1):e*c;return{buy_price:s(e,2),sell_price:s(o,2),quantity:s(a,8),buy_fee_percent:s(n,4),sell_fee_percent:s(r,4),network_fee:s(i,4),invested:s(p,2),returned:s(m,2),profit:s(d,2),profit_before_network_fee:s(l,2),roi_percent:_(h),break_even_price:s(M,2),is_profit:d>=0}}import{z as x}from"zod";var _e=x.object({coin:x.string(),date:x.string().optional().describe("Single date DD-MM-YYYY. Use with investment snapshot."),days:x.union([x.number(),x.string()]).optional().describe("Chart range: 1, 7, 14, 30, 90, 180, 365, or max"),vs_currency:x.string().default("usd"),investment:x.number().default(1e3).describe("Hypothetical investment amount on the historical date")});function Fe(t){let e=t.match(/^(\d{2})-(\d{2})-(\d{4})$/);if(!e)throw new Error(`Invalid date format: ${t}. Expected DD-MM-YYYY (e.g. 15-01-2024)`);let[,o,a,n]=e,r=`${o}-${a}-${n}`,i=`${n}-${a}-${o}`,c=new Date(i);if(isNaN(c.getTime()))throw new Error(`Invalid date: ${t}`);let u=new Date;if(u.setHours(23,59,59,999),c>u)throw new Error(`Date cannot be in the future: ${t}`);return{formatted:r,iso:i}}function De(t){if(t.length===0)return null;let e=t.map(m=>m.price),o=e[0],a=e[e.length-1],n=Math.max(...e),r=Math.min(...e),i=a-o,c=o>0?i/o*100:0,u=Math.max(1,Math.floor(t.length/12)),p=t.filter((m,l)=>l%u===0||l===t.length-1).map(m=>({time:new Date(m.timestamp).toISOString(),price:s(m.price,g("usd",m.price))}));return{start_price:s(o,g("usd",o)),end_price:s(a,g("usd",a)),high:s(n,g("usd",n)),low:s(r,g("usd",r)),change:s(i,4),change_percent:_(c),points:t.length,sample:p}}async function ye(t){let e=(t.vs_currency??"usd").toLowerCase(),o=t.investment??1e3,a=f(t.coin);if(!t.date&&!t.days)throw new Error("Provide either date (DD-MM-YYYY) for a point-in-time price, or days (e.g. 7, 30, 90) for a range chart.");if(t.days!==void 0&&t.days!==null){let l=await ae(t.coin,e,t.days),d=De(l.prices);return{mode:"range",coin:a,vs_currency:e,days:String(t.days),summary:d}}let{formatted:n}=Fe(t.date),r=await oe(t.coin,n,e),i=await se(t.coin,e),c=r.price>0?o/r.price:0,u=c*i,p=u-o,m=o>0?p/o*100:0;return{mode:"point",coin:a,date:n,vs_currency:e,historical_price:s(r.price,g(e,r.price)),current_price:s(i,g(e,i)),price_change:s(i-r.price,4),price_change_percent:_(r.price>0?(i-r.price)/r.price*100:0),investment_then:{invested:o,coins_bought:s(c,8),worth_now:s(u,2),gain_loss:s(p,2),gain_loss_percent:_(m)}}}import{z as W}from"zod";var be=W.object({query:W.string().describe("Name or symbol to search, e.g. pepe, solana, wif"),limit:W.number().int().min(1).max(50).default(10)});async function we(t){let e=t.limit??10,o=await ne(t.query,e);return{query:t.query,count:o.length,results:o}}import{z as U}from"zod";var ke=U.object({vs_currency:U.string().default("usd"),limit:U.number().int().min(1).max(50).default(10),order:U.enum(["market_cap_desc","market_cap_asc","volume_desc","volume_asc","id_asc","id_desc"]).default("market_cap_desc")});async function ve(t){let e=(t.vs_currency??"usd").toLowerCase(),o=t.limit??10,a=t.order??"market_cap_desc",r=(await q(e,{perPage:o,order:a})).map(p=>({id:p.id,symbol:p.symbol,name:p.name,rank:p.market_cap_rank,price:s(p.current_price??0,g(e,p.current_price??0)),market_cap:p.market_cap?s(p.market_cap,0):null,total_volume:p.total_volume?s(p.total_volume,0):null,"24h_change":p.price_change_percentage_24h?_(p.price_change_percentage_24h):null})),i=r.filter(p=>p["24h_change"]!==null),c=[...i].sort((p,m)=>(m["24h_change"]??0)-(p["24h_change"]??0)).slice(0,3),u=[...i].sort((p,m)=>(p["24h_change"]??0)-(m["24h_change"]??0)).slice(0,3);return{vs_currency:e,order:a,count:r.length,coins:r,top_gainers:c,top_losers:u}}import{z as E}from"zod";var Pe=E.object({coins:E.union([E.array(E.string()).min(2).max(5),E.string().describe("Comma-separated symbols, e.g. btc,eth,sol")]).describe("2\u20135 coins to compare"),vs_currency:E.string().default("usd")});function ze(t){let e=Array.isArray(t)?t:t.split(/[,\s]+/).map(a=>a.trim()).filter(Boolean),o=[...new Set(e.map(a=>a.toLowerCase()))];if(o.length<2||o.length>5)throw new Error("Provide between 2 and 5 coins to compare");return o}async function Ce(t){let e=(t.vs_currency??"usd").toLowerCase(),o=ze(t.coins),a=o.map(f),n=await q(e,{ids:a,perPage:a.length}),r=new Map(n.map(m=>[m.id,m])),i=a.map((m,l)=>{let d=r.get(m);return d?{input:o[l],id:d.id,found:!0,symbol:d.symbol,name:d.name,rank:d.market_cap_rank,price:s(d.current_price??0,g(e,d.current_price??0)),market_cap:d.market_cap?s(d.market_cap,0):null,total_volume:d.total_volume?s(d.total_volume,0):null,"24h_change":d.price_change_percentage_24h?_(d.price_change_percentage_24h):null}:{input:o[l],id:m,found:!1}}),c=i.filter(m=>m.found),u=[...c].sort((m,l)=>(l["24h_change"]??-1/0)-(m["24h_change"]??-1/0))[0],p=[...c].sort((m,l)=>(l.market_cap??0)-(m.market_cap??0))[0];return{vs_currency:e,coins:i,highlights:{best_24h_performer:u?.id??null,largest_market_cap:p?.id??null}}}import{z as $}from"zod";var Ue=$.object({coin:$.string(),amount:$.number().positive()}),Se=$.object({holdings:$.array(Ue).min(1).max(50).describe("List of { coin, amount } holdings"),vs_currency:$.string().default("usd")});async function xe(t){let e=(t.vs_currency??"usd").toLowerCase(),o=t.holdings,a=o.map(l=>f(l.coin)),n=[...new Set(a)],r=e==="pkr"?"usd":e,i=await P(n,[r]),c=null,u=null;if(e==="pkr"){let l=await D(1,"usdt");c=l.rate,u=l.source}let p=o.map((l,d)=>{let v=a[d],h=i[v]?.[r];if(h===void 0)return{coin:v,amount:l.amount,found:!1,value:null};let M=l.amount*h,H=h;return e==="pkr"&&c!==null&&(M*=c,H*=c),{coin:v,amount:l.amount,found:!0,unit_price:s(H,g(e,H)),value:s(M,2)}}),m=p.reduce((l,d)=>l+(d.found&&d.value!==null?d.value:0),0);return{vs_currency:e,holdings:p,total_value:s(m,2),missing:p.filter(l=>!l.found).map(l=>l.coin),...u?{rate_source:u}:{}}}var Ve=[{name:"get_price",description:"Get live price of any cryptocurrency in any fiat, with market cap, volume, rank, ATH/ATL. Uses CoinGecko.",inputSchema:{type:"object",properties:{coin:{type:"string",description:"Coin id or symbol: btc, bitcoin, eth, sol, etc"},vs_currency:{type:"string",description:"Fiat: usd, eur, pkr, inr, gbp, etc",default:"usd"}},required:["coin"]}},{name:"convert",description:"Convert crypto amount to fiat/crypto. Optimized PKR via Binance P2P when requested.",inputSchema:{type:"object",properties:{amount:{type:"number"},from:{type:"string"},to:{type:"string",description:"usd, eur, pkr, btc, eth, etc"}},required:["amount","from","to"]}},{name:"gas_tracker",description:"Live gas fees for EVM chains (eth, bnb, polygon, arbitrum, base, optimism) with estimated transfer cost in native + USD.",inputSchema:{type:"object",properties:{chain:{type:"string",enum:["eth","bnb","polygon","arbitrum","base","optimism"],default:"eth"}}}},{name:"profit_calc",description:"Calculate crypto profit/loss, ROI, break-even. Supports exchange fees and fixed network/gas fee. No API needed.",inputSchema:{type:"object",properties:{buy_price:{type:"number"},sell_price:{type:"number"},quantity:{type:"number"},buy_fee_percent:{type:"number",default:0},sell_fee_percent:{type:"number",default:0},network_fee:{type:"number",default:0,description:"Fixed network fee in same unit as prices"}},required:["buy_price","sell_price","quantity"]}},{name:"historical_price",description:"Historical price on a date (DD-MM-YYYY) with investment snapshot, or a range chart via days (1/7/30/90/365/max).",inputSchema:{type:"object",properties:{coin:{type:"string"},date:{type:"string",description:"DD-MM-YYYY for point-in-time"},days:{type:["string","number"],description:"Range chart: 1, 7, 14, 30, 90, 180, 365, or max"},vs_currency:{type:"string",default:"usd"},investment:{type:"number",default:1e3,description:"Hypothetical investment on the historical date"}},required:["coin"]}},{name:"search_coin",description:"Search cryptocurrencies by name or symbol. Returns CoinGecko ids for use in other tools.",inputSchema:{type:"object",properties:{query:{type:"string"},limit:{type:"number",default:10}},required:["query"]}},{name:"top_coins",description:"Top cryptocurrencies by market cap or volume, with gainers/losers among the result set.",inputSchema:{type:"object",properties:{vs_currency:{type:"string",default:"usd"},limit:{type:"number",default:10},order:{type:"string",enum:["market_cap_desc","market_cap_asc","volume_desc","volume_asc"],default:"market_cap_desc"}}}},{name:"compare_coins",description:"Compare 2\u20135 cryptocurrencies side by side (price, mcap, volume, 24h change).",inputSchema:{type:"object",properties:{coins:{oneOf:[{type:"string"},{type:"array",items:{type:"string"}}],description:"Array or comma-separated list, e.g. btc,eth,sol"},vs_currency:{type:"string",default:"usd"}},required:["coins"]}},{name:"portfolio_value",description:"Value a portfolio of holdings in any fiat (batch pricing). Pass [{coin, amount}, ...].",inputSchema:{type:"object",properties:{holdings:{type:"array",items:{type:"object",properties:{coin:{type:"string"},amount:{type:"number"}},required:["coin","amount"]}},vs_currency:{type:"string",default:"usd"}},required:["holdings"]}}],Je=[{name:"market-overview",description:"Global crypto market snapshot using top coins and gainers/losers",arguments:[{name:"vs_currency",description:"Fiat currency (default usd)",required:!1},{name:"limit",description:"How many top coins (default 10)",required:!1}]},{name:"analyze-coin",description:"Analyze a coin: live price, 7d history, and optional conversion",arguments:[{name:"coin",description:"Coin symbol or id",required:!0},{name:"vs_currency",description:"Fiat currency (default usd)",required:!1}]},{name:"trade-pnl",description:"Walk through a profit/loss calculation for a completed trade",arguments:[{name:"buy_price",description:"Entry price",required:!0},{name:"sell_price",description:"Exit price",required:!0},{name:"quantity",description:"Amount traded",required:!0},{name:"fees",description:"Optional fee notes (percent or network)",required:!1}]},{name:"gas-check",description:"Check current gas and estimated transfer cost for an EVM chain",arguments:[{name:"chain",description:"eth, bnb, polygon, arbitrum, base, or optimism",required:!1}]}];function Q(t){return{content:[{type:"text",text:t}],isError:!0}}function C(t){return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}function Re(t){t.setRequestHandler(Ke,async()=>({tools:Ve})),t.setRequestHandler(Ye,async()=>({prompts:Je})),t.setRequestHandler(Be,async e=>{let{name:o,arguments:a}=e.params,n=a??{};switch(o){case"market-overview":{let r=n.vs_currency??"usd",i=n.limit??"10";return{description:"Market overview workflow",messages:[{role:"user",content:{type:"text",text:`Give me a crypto market overview in ${r}. Use the top_coins tool with limit ${i}. Summarize leaders, notable gainers/losers, and overall tone.`}}]}}case"analyze-coin":{let r=n.coin;if(!r)throw new Error("coin is required");let i=n.vs_currency??"usd";return{description:`Analyze ${r}`,messages:[{role:"user",content:{type:"text",text:`Analyze ${r} in ${i}. 1) Call get_price. 2) Call historical_price with days=7. 3) Summarize price, market cap, 24h move, and 7-day trend.`}}]}}case"trade-pnl":{let{buy_price:r,sell_price:i,quantity:c,fees:u}=n;if(!r||!i||!c)throw new Error("buy_price, sell_price, and quantity are required");return{description:"Trade P&L workflow",messages:[{role:"user",content:{type:"text",text:`Calculate P&L with profit_calc: buy_price=${r}, sell_price=${i}, quantity=${c}.${u?` Fee notes: ${u}.`:""} Explain profit, ROI, and break-even clearly.`}}]}}case"gas-check":{let r=n.chain??"eth";return{description:`Gas check for ${r}`,messages:[{role:"user",content:{type:"text",text:`Check gas on ${r} with gas_tracker. Report low/average/high gwei and estimated simple-transfer cost in USD. Advise whether fees look cheap or expensive.`}}]}}default:throw new Error(`Unknown prompt: ${o}`)}}),t.setRequestHandler(He,async e=>{let{name:o,arguments:a}=e.params;try{switch(o){case"get_price":return C(await ue(ce.parse(a??{})));case"convert":return C(await le(pe.parse(a??{})));case"gas_tracker":return C(await he(de.parse(a??{})));case"profit_calc":return C(fe(ge.parse(a??{})));case"historical_price":return C(await ye(_e.parse(a??{})));case"search_coin":return C(await we(be.parse(a??{})));case"top_coins":return C(await ve(ke.parse(a??{})));case"compare_coins":return C(await Ce(Pe.parse(a??{})));case"portfolio_value":return C(await xe(Se.parse(a??{})));default:return Q(`Unknown tool: ${o}`)}}catch(n){if(n instanceof y&&n.retryable)return Q(`Rate limit hit: ${n.message}. Wait a few seconds and retry.`);let r=n instanceof Error?n.message:"Unknown error occurred";return Q(r)}})}var Ae=Number(process.env.PORT??3e3),Ee=JSON.parse(Qe(et(Ze(Xe(import.meta.url)),"../package.json"),"utf8")),N={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"GET, POST, OPTIONS","Access-Control-Allow-Headers":"content-type, accept, mcp-session-id, mcp-protocol-version","Access-Control-Expose-Headers":"mcp-session-id"};function Te(){let t=new tt({name:"mcp-crypto-toolkit",version:Ee.version,description:"Live crypto prices, conversion, gas tracker, portfolio tools and calculators for AI agents"},{capabilities:{tools:{},prompts:{}}});return Re(t),t}function rt(t){return new Promise((e,o)=>{let a=[];t.on("data",n=>a.push(n)),t.on("end",()=>e(Buffer.concat(a).toString("utf8"))),t.on("error",o)})}function nt(t){if(!t||typeof t!="object")return!1;let e=t;return typeof e.method=="string"&&!("id"in e)}var ot=We(async(t,e)=>{try{let o=t.url?.split("?")[0]??"/";if(t.method==="OPTIONS"&&o.startsWith("/mcp")){e.writeHead(204,{...N,"Access-Control-Max-Age":"86400"}),e.end();return}if(t.method==="GET"&&(o==="/"||o==="/health")){let a=JSON.stringify({ok:!0,name:"mcp-crypto-toolkit",version:Ee.version,mcp:"/mcp"});e.writeHead(200,{"Content-Type":"application/json","Content-Length":Buffer.byteLength(a),...N}),e.end(a);return}if(o.startsWith("/mcp")){if(t.method==="POST"){let r=await rt(t),i;try{i=r?JSON.parse(r):void 0}catch{let p=JSON.stringify({jsonrpc:"2.0",error:{code:-32700,message:"Parse error"},id:null});e.writeHead(400,{"Content-Type":"application/json","Content-Length":Buffer.byteLength(p),...N}),e.end(p);return}if(nt(i)){let p=`{"ok":true}
3
- `;e.writeHead(202,{"Content-Type":"application/json","Content-Length":Buffer.byteLength(p),...N}),e.end(p);return}let c=Te(),u=new Ie({sessionIdGenerator:void 0,enableJsonResponse:!0});e.on("close",()=>{u.close(),c.close()}),await c.connect(u),await u.handleRequest(t,e,i);return}let a=Te(),n=new Ie({sessionIdGenerator:void 0,enableJsonResponse:!0});e.on("close",()=>{n.close(),a.close()}),await a.connect(n),await n.handleRequest(t,e);return}e.writeHead(404,{"Content-Type":"text/plain",...N}),e.end("Not found. Use POST /mcp or GET /health")}catch(o){console.error("HTTP error:",o),e.headersSent||(e.writeHead(500,{"Content-Type":"text/plain",...N}),e.end("Internal server error"))}});ot.listen(Ae,()=>{console.error(`mcp-crypto-toolkit HTTP listening on http://localhost:${Ae}/mcp`)});
2
+ import{createServer as st}from"http";import{StreamableHTTPServerTransport as Me}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{Server as at}from"@modelcontextprotocol/sdk/server/index.js";import{CallToolRequestSchema as Xe,ListToolsRequestSchema as Ze,ListPromptsRequestSchema as et,GetPromptRequestSchema as tt}from"@modelcontextprotocol/sdk/types.js";var te={btc:"bitcoin",bitcoin:"bitcoin",eth:"ethereum",ethereum:"ethereum",usdt:"tether",tether:"tether",bnb:"binancecoin",binancecoin:"binancecoin",sol:"solana",solana:"solana",xrp:"ripple",ripple:"ripple",usdc:"usd-coin","usd-coin":"usd-coin",ada:"cardano",cardano:"cardano",avax:"avalanche-2",avalanche:"avalanche-2",doge:"dogecoin",dogecoin:"dogecoin",dot:"polkadot",polkadot:"polkadot",trx:"tron",tron:"tron",link:"chainlink",chainlink:"chainlink",matic:"matic-network",polygon:"matic-network","matic-network":"matic-network",shib:"shiba-inu","shiba-inu":"shiba-inu",ltc:"litecoin",litecoin:"litecoin",bch:"bitcoin-cash","bitcoin-cash":"bitcoin-cash",uni:"uniswap",uniswap:"uniswap",atom:"cosmos",cosmos:"cosmos",xlm:"stellar",stellar:"stellar",xmr:"monero",monero:"monero",etc:"ethereum-classic","ethereum-classic":"ethereum-classic",fil:"filecoin",filecoin:"filecoin",apt:"aptos",aptos:"aptos",arb:"arbitrum",arbitrum:"arbitrum",op:"optimism",optimism:"optimism",near:"near",vet:"vechain",vechain:"vechain",icp:"internet-computer",hbar:"hedera-hashgraph",algo:"algorand",algorand:"algorand",qnt:"quant-network",eos:"eos",aave:"aave",grt:"the-graph",sand:"the-sandbox",mana:"decentraland",axs:"axie-infinity",theta:"theta-token",ftm:"fantom",fantom:"fantom",xtz:"tezos",tezos:"tezos",rune:"thorchain",snx:"havven",crv:"curve-dao-token",mkr:"maker",maker:"maker",comp:"compound-governance-token",ldo:"lido-dao",sui:"sui",sei:"sei-network",inj:"injective-protocol",tia:"celestia",pepe:"pepe",wif:"dogwifcoin",bonk:"bonk",floki:"floki",render:"render-token",rndr:"render-token",fet:"fetch-ai",imx:"immutable-x",gala:"gala",enj:"enjincoin",bat:"basic-attention-token",zec:"zcash",dash:"dash",neo:"neo",kcs:"kucoin-shares",okb:"okb",ton:"the-open-network",kas:"kaspa",stx:"blockstack",rpl:"rocket-pool",blur:"blur",pendle:"pendle",jup:"jupiter-exchange-solana",pyth:"pyth-network",wld:"worldcoin-wld",strk:"starknet",ethfi:"ether-fi",ena:"ethena",ondo:"ondo-finance",not:"notcoin",bome:"book-of-meme",ar:"arweave",flow:"flow",mina:"mina-protocol",ro:"ronin",ronin:"ronin",egld:"elrond-erd-2",egld_m:"elrond-erd-2",cake:"pancakeswap-token","1inch":"1inch",ens:"ethereum-name-service",chz:"chiliz",hot:"holotoken",zil:"zilliqa",icx:"icon",waves:"waves",kava:"kava",celo:"celo",rose:"oasis-network",one:"harmony",iota:"iota",xem:"nem",sc:"siacoin",dcr:"decred",rvn:"ravencoin",zen:"zencash",ont:"ontology",qtum:"qtum",btt:"bittorrent",hnt:"helium",lrc:"loopring",ankr:"ankr",omg:"omisego",storj:"storj",skl:"skale",celr:"celer-network",coti:"coti",ren:"republic-protocol",band:"band-protocol",ocean:"ocean-protocol",api3:"api3",nmr:"numeraire",bal:"balancer",yfi:"yearn-finance",sushi:"sushi",dydx:"dydx-chain",gmx:"gmx",magic:"magic",rdnt:"radiant-capital",joe:"joe",woo:"woo-network",perp:"perpetual-protocol",lqty:"liquity",cvx:"convex-finance",frax:"frax",fxn:"fxn",usdd:"usdd",tusd:"true-usd",dai:"dai",frax_usd:"frax"};var re={eth:1,bnb:56,polygon:137,arbitrum:42161,base:8453,optimism:10},ne={eth:"ethereum",bnb:"binancecoin",polygon:"matic-network",arbitrum:"ethereum",base:"ethereum",optimism:"ethereum"},B=21e3,oe={eth:{url:"https://api.etherscan.io/api",apiKeyEnv:"ETHERSCAN_API_KEY"},bnb:{url:"https://api.bscscan.com/api",apiKeyEnv:"BSCSCAN_API_KEY"},polygon:{url:"https://api.polygonscan.com/api",apiKeyEnv:"POLYGONSCAN_API_KEY"},arbitrum:{url:"https://api.arbiscan.io/api",apiKeyEnv:"ARBISCAN_API_KEY"},base:{url:"https://api.basescan.org/api",apiKeyEnv:"BASESCAN_API_KEY"},optimism:{url:"https://api-optimistic.etherscan.io/api",apiKeyEnv:"OPTIMISM_API_KEY"}},I="mcp-crypto-toolkit/1.0.0 (https://github.com/nad33mahm3d/mcp-crypto-toolkit)";var T=class{store=new Map;timers=new Map;get(e){let o=this.store.get(e);if(o){if(Date.now()>o.expiresAt){this.delete(e);return}return o.value}}set(e,o,a){let n=this.timers.get(e);n&&clearTimeout(n),this.store.set(e,{value:o,expiresAt:Date.now()+a});let r=setTimeout(()=>{this.delete(e)},a);r.unref?.(),this.timers.set(e,r)}clear(){for(let e of this.timers.values())clearTimeout(e);this.timers.clear(),this.store.clear()}delete(e){let o=this.timers.get(e);o&&(clearTimeout(o),this.timers.delete(e)),this.store.delete(e)}},v=new T,V=new T,O=new T,L=new T,k={PRICE:6e4,GAS:15e3,HISTORICAL:36e5,PKR:3e5,SEARCH:12e4,TRENDING:18e4};var N="https://api.coingecko.com/api/v3",w=class extends Error{constructor(o,a,n=!1){super(o);this.status=a;this.retryable=n;this.name="CoinGeckoError"}status;retryable};function f(t){let e=t.toLowerCase().trim();return te[e]??e}function Le(){let t=process.env.COINGECKO_API_KEY?.trim();return t?t.startsWith("CG-")&&process.env.COINGECKO_PRO==="1"?{"x-cg-pro-api-key":t}:{"x-cg-demo-api-key":t}:{}}async function E(t,e={},o=3){let a;for(let n=0;n<o;n++)try{let r=await fetch(t,{...e,headers:{Accept:"application/json","User-Agent":I,...Le(),...e.headers??{}}});if(r.status===429){let i=Math.min(1e3*2**n,1e4);if(n<o-1){await ae(i);continue}throw new w("CoinGecko rate limit exceeded (429). Wait a few seconds, or set COINGECKO_API_KEY for higher limits.",429,!0)}if(!r.ok)throw new w(`CoinGecko API error: ${r.status} ${r.statusText}`,r.status,r.status>=500);return r}catch(r){if(r instanceof w)throw r;a=r instanceof Error?r:new Error(String(r)),n<o-1&&await ae(1e3*2**n)}throw a??new Error("Fetch failed after retries")}async function P(t,e){let o=[...new Set(t.map(f))],a=[...new Set(e.map(p=>p.toLowerCase()))],n=`simple:${o.sort().join(",")}:${a.sort().join(",")}`,r=v.get(n);if(r)return r;let i=new URLSearchParams({ids:o.join(","),vs_currencies:a.join(","),include_24hr_change:"true",include_last_updated_at:"true"}),u=await(await E(`${N}/simple/price?${i}`)).json();return v.set(n,u,k.PRICE),u}async function x(t,e){let o=f(t),a=e.toLowerCase(),r=(await P([o],[a]))[o];if(!r)throw new w(`Coin not found: ${t} (id: ${o})`);let i=r[a];if(i===void 0)throw new w(`Currency not supported: ${e}`);let c=`${a}_24h_change`,u="last_updated_at";return{id:o,price:Number(i),change24h:r[c]!==void 0?Number(r[c]):void 0,lastUpdated:r[u]!==void 0?new Date(Number(r[u])*1e3).toISOString():new Date().toISOString()}}async function F(t,e={}){let o=t.toLowerCase(),a=e.perPage??10,n=e.page??1,r=e.order??"market_cap_desc",i=e.ids?.map(f),c=`markets:${o}:${i?.sort().join(",")??"all"}:${a}:${n}:${r}`,u=v.get(c);if(u)return u;let p=new URLSearchParams({vs_currency:o,order:r,per_page:String(a),page:String(n),sparkline:"false",price_change_percentage:"24h"});i?.length&&p.set("ids",i.join(","));let l=await(await E(`${N}/coins/markets?${p}`)).json();return v.set(c,l,k.PRICE),l}async function J(t,e){let o=f(t),n=(await F(e,{ids:[o],perPage:1}))[0];if(!n)throw new w(`Coin not found: ${t} (id: ${o})`);return n}async function se(t,e=10){let o=t.trim();if(!o)return[];let a=`search:${o.toLowerCase()}:${e}`,n=v.get(a);if(n)return n;let c=((await(await E(`${N}/search?query=${encodeURIComponent(o)}`)).json()).coins??[]).slice(0,e).map(u=>({id:u.id,name:u.name,symbol:u.symbol,market_cap_rank:u.market_cap_rank}));return v.set(a,c,k.PRICE),c}async function ie(t,e,o){let a=f(t),n=o.toLowerCase(),r=`hist:${a}:${e}:${n}`,i=O.get(r);if(i)return i;let u=await(await E(`${N}/coins/${a}/history?date=${encodeURIComponent(e)}&localization=false`)).json();if(u.error||!u.market_data?.current_price?.[n])throw new w(u.error??`Historical price not found for ${t} on ${e}`);let p={id:a,date:e,price:u.market_data.current_price[n],vsCurrency:n};return O.set(r,p,k.HISTORICAL),p}async function ce(t,e,o){let a=f(t),n=e.toLowerCase(),r=String(o),i=`chart:${a}:${n}:${r}`,c=O.get(i);if(c)return c;let u=new URLSearchParams({vs_currency:n,days:r}),l=((await(await E(`${N}/coins/${a}/market_chart?${u}`)).json()).prices??[]).map(([C,g])=>({timestamp:C,price:g})),d={id:a,vs:n,days:r,prices:l};return O.set(i,d,k.HISTORICAL),d}async function ue(t,e){return(await x(t,e)).price}async function pe(){let t="trending:coins",e=v.get(t);if(e)return e;let n=((await(await E(`${N}/search/trending`)).json()).coins??[]).map(({item:r})=>({id:r.id,name:r.name,symbol:r.symbol,market_cap_rank:r.market_cap_rank,score:r.score,price_btc:r.price_btc??null,price_usd:typeof r.data?.price=="number"?s(r.data.price,h("usd",r.data.price)):null,change_24h_usd:typeof r.data?.price_change_percentage_24h?.usd=="number"?y(r.data.price_change_percentage_24h.usd):null}));return v.set(t,n,k.TRENDING),n}async function le(){let t="fear-greed:1",e=v.get(t);if(e)return e;let n=(await(await E("https://api.alternative.me/fng/?limit=1")).json()).data?.[0];if(!n)throw new w("Fear & Greed index unavailable");let r={value:Number(n.value),classification:n.value_classification,updatedAt:new Date(Number(n.timestamp)*1e3).toISOString()};return v.set(t,r,k.TRENDING),r}function ae(t){return new Promise(e=>setTimeout(e,t))}function s(t,e=2){return Number.isFinite(t)?Number(t.toFixed(e)):0}function y(t,e=2){return Number.isFinite(t)?Number(t.toFixed(e)):0}function h(t,e){return t==="btc"||t==="eth"?8:e>=1?2:e>=.01?4:8}import{z as W}from"zod";async function M(){let t=L.get("usdt-pkr");if(t)return t;try{let o={rate:await qe(),source:"binance_p2p",lastUpdated:new Date().toISOString()};return L.set("usdt-pkr",o,k.PKR),o}catch{let o={rate:await ze(),source:"coingecko",lastUpdated:new Date().toISOString()};return L.set("usdt-pkr",o,k.PKR),o}}async function qe(){let t=await fetch("https://p2p.binance.com/bapi/c2c/v2/friendly/c2c/adv/search",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json","User-Agent":I},body:JSON.stringify({asset:"USDT",fiat:"PKR",merchantCheck:!1,page:1,payTypes:[],publisherType:null,rows:10,tradeType:"BUY"})});if(!t.ok)throw new Error(`Binance P2P API error: ${t.status}`);let o=(await t.json()).data?.slice(0,5)??[];if(o.length===0)throw new Error("No Binance P2P ads found for USDT/PKR");let a=o.map(r=>parseFloat(r.adv.price)).filter(r=>!isNaN(r));if(a.length===0)throw new Error("Invalid Binance P2P price data");let n=a.reduce((r,i)=>r+i,0)/a.length;return Number(n.toFixed(2))}async function ze(){let e=(await P(["tether"],["pkr"])).tether?.pkr;if(e===void 0)throw new w("Could not fetch USDT/PKR rate from CoinGecko");return Number(e.toFixed(2))}async function q(t,e){let o=e.toLowerCase();if(o==="pkr")return{converted:t,rate:1,source:"direct"};if(o==="usdt"||o==="tether"){let u=await M();return{converted:Number((t*u.rate).toFixed(2)),rate:u.rate,source:u.source}}let a=f(o),r=(await P([a],["usd"]))[a]?.usd;if(r===void 0)throw new w(`Could not get USD price for ${e}`);let i=t*r,c=await M();return{converted:Number((i*c.rate).toFixed(2)),rate:Number((r*c.rate).toFixed(2)),source:`coingecko_usd + ${c.source}`}}async function me(t,e){let o=e.toLowerCase();if(o==="pkr")return{converted:t,rate:1,source:"direct"};let a=await M(),n=t/a.rate;if(o==="usdt"||o==="tether"||o==="usd")return{converted:Number(n.toFixed(6)),rate:Number((1/a.rate).toFixed(8)),source:a.source};let r=f(o),c=(await P([r],["usd"]))[r]?.usd;if(!c)throw new w(`Could not get USD price for ${e}`);let u=n/c;return{converted:Number(u.toFixed(8)),rate:Number((a.rate/c).toFixed(2)),source:`coingecko_usd + ${a.source}`}}var de=W.object({coin:W.string().describe("Coin id or symbol: btc, bitcoin, eth, sol, etc"),vs_currency:W.string().default("usd").describe("Fiat: usd, eur, pkr, inr, gbp, etc")});async function he(t){let{coin:e,vs_currency:o}=t,a=o.toLowerCase();if(a==="pkr"){let n=await M(),r=await J(e,"usd"),i=(r.current_price??0)*n.rate;return{coin:r.id,symbol:r.symbol,name:r.name,price:s(i,2),vs_currency:"pkr","24h_change":r.price_change_percentage_24h?y(r.price_change_percentage_24h):null,market_cap:r.market_cap?s(r.market_cap*n.rate,0):null,market_cap_rank:r.market_cap_rank,total_volume:r.total_volume?s(r.total_volume*n.rate,0):null,high_24h:r.high_24h?s(r.high_24h*n.rate,2):null,low_24h:r.low_24h?s(r.low_24h*n.rate,2):null,ath:r.ath?s(r.ath*n.rate,2):null,ath_change_percent:r.ath_change_percentage?y(r.ath_change_percentage):null,usd_price:s(r.current_price,2),rate_source:n.source,last_updated:r.last_updated??new Date().toISOString()}}try{let n=await J(e,a),r=n.current_price??0;return{coin:n.id,symbol:n.symbol,name:n.name,price:s(r,h(a,r)),vs_currency:a,"24h_change":n.price_change_percentage_24h?y(n.price_change_percentage_24h):null,market_cap:n.market_cap?s(n.market_cap,0):null,market_cap_rank:n.market_cap_rank,total_volume:n.total_volume?s(n.total_volume,0):null,high_24h:n.high_24h?s(n.high_24h,h(a,n.high_24h)):null,low_24h:n.low_24h?s(n.low_24h,h(a,n.low_24h)):null,ath:n.ath?s(n.ath,h(a,n.ath)):null,ath_change_percent:n.ath_change_percentage?y(n.ath_change_percentage):null,atl:n.atl?s(n.atl,h(a,n.atl)):null,last_updated:n.last_updated??new Date().toISOString()}}catch{let n=await x(e,a);return{coin:n.id,price:s(n.price,h(a,n.price)),vs_currency:a,"24h_change":n.change24h?y(n.change24h):null,last_updated:n.lastUpdated??new Date().toISOString()}}}import{z}from"zod";var ge=z.object({amount:z.number(),from:z.string(),to:z.string().describe("usd, pkr, btc, eth, etc")});async function fe(t){let{amount:e,from:o,to:a}=t,n=o.toLowerCase(),r=a.toLowerCase();if(n===r)return{amount:e,from:n,to:r,converted:e,rate:1,source:"direct"};if(r==="pkr"){let g=await q(e,n);return{amount:e,from:f(n),to:"pkr",converted:g.converted,rate:g.rate,source:g.source}}if(n==="pkr"){let g=await me(e,r);return{amount:e,from:"pkr",to:f(r),converted:g.converted,rate:g.rate,source:g.source}}let i=f(n),c=f(r),u=new Set(["usd","eur","gbp","jpy","inr","aud","cad","chf","cny"]);if(u.has(r)){let g=await x(n,r);return{amount:e,from:i,to:r,converted:s(e*g.price,2),rate:s(g.price,2),source:"coingecko"}}if(u.has(n)){let g=await x(r,n);return{amount:e,from:n,to:c,converted:s(e/g.price,8),rate:s(1/g.price,8),source:"coingecko"}}let p=await P([i,c],["usd"]),m=p[i]?.usd,l=p[c]?.usd;if(!m||!l)throw new Error(`Could not convert ${o} to ${a}`);let d=m/l,C=e*d;return{amount:e,from:i,to:c,converted:s(C,8),rate:s(d,8),source:"coingecko"}}import{z as _e}from"zod";var Ue=["eth","bnb","polygon","arbitrum","base","optimism"],be=_e.object({chain:_e.enum(Ue).default("eth")});async function ye(t){let e=t.chain,o=`gas:${e}`,a=V.get(o);if(a)return a;let n,r=oe[e],i=r?process.env[r.apiKeyEnv]:void 0;if(r&&i)try{n=await Ke(e,r.url,i)}catch{}n||(n=await Ye(e));let c=await He(n);return V.set(o,c,k.GAS),c}function Q(t){return t*B/1e9}async function He(t){let e=ne[t.chain]??"ethereum",o=null;try{o=(await x(e,"usd")).price}catch{o=null}let a=Q(t.low),n=Q(t.average),r=Q(t.high);return{...t,estimated_transfer:{gas_units:B,cost_native_low:s(a,8),cost_native_average:s(n,8),cost_native_high:s(r,8),cost_usd_low:o!==null?s(a*o,4):null,cost_usd_average:o!==null?s(n*o,4):null,cost_usd_high:o!==null?s(r*o,4):null,native_token:e}}}async function Ke(t,e,o){let a=`${e}?module=gastracker&action=gasoracle&apikey=${o}`,n=await fetch(a,{headers:{"User-Agent":I,Accept:"application/json"}});if(!n.ok)throw new Error(`Scan API error: ${n.status}`);let r=await n.json();if(r.status!=="1"||!r.result)throw new Error("Invalid scan gas oracle response");return{chain:t,low:s(parseFloat(r.result.SafeGasPrice??"0"),2),average:s(parseFloat(r.result.ProposeGasPrice??"0"),2),high:s(parseFloat(r.result.FastGasPrice??"0"),2),baseFee:r.result.suggestBaseFee?s(parseFloat(r.result.suggestBaseFee),2):null,unit:"gwei",last_updated:new Date().toISOString(),source:`${t}scan`}}async function Ye(t){let e=re[t];if(!e)throw new Error(`Unsupported chain: ${t}`);try{let o=await fetch(`https://api.blocknative.com/gasprices/blockprices?chainid=${e}`,{headers:{Accept:"application/json","User-Agent":I}});if(o.ok){let n=(await o.json()).blockPrices?.[0],r=n?.estimatedPrices??[];if(r.length>0){let i=[...r].sort((l,d)=>l.price-d.price),c=i[0]?.price??0,u=i[i.length-1]?.price??0,p=i[Math.floor(i.length/2)]?.price??c,m=n?.baseFeePerGas?n.baseFeePerGas/1e9:null;return{chain:t,low:s(c,2),average:s(p,2),high:s(u,2),baseFee:m!==null?s(m,2):null,unit:"gwei",last_updated:new Date().toISOString(),source:"blocknative"}}}}catch{}return Be(t)}function Be(t){let e={eth:{low:15,average:25,high:40},bnb:{low:3,average:5,high:8},polygon:{low:30,average:50,high:80},arbitrum:{low:.1,average:.2,high:.5},base:{low:.01,average:.05,high:.1},optimism:{low:.01,average:.05,high:.1}},o=e[t]??e.eth;return{chain:t,low:o.low,average:o.average,high:o.high,baseFee:null,unit:"gwei",last_updated:new Date().toISOString(),source:"static_estimate"}}import{z as A}from"zod";var we=A.object({buy_price:A.number(),sell_price:A.number(),quantity:A.number(),buy_fee_percent:A.number().default(0),sell_fee_percent:A.number().default(0),network_fee:A.number().default(0).describe("Fixed network/gas fee in the same unit as prices (e.g. USD)")});function ke(t){let e=t.buy_price,o=t.sell_price,a=t.quantity,n=t.buy_fee_percent??0,r=t.sell_fee_percent??0,i=t.network_fee??0,c=1+n/100,u=1-r/100,p=e*a*c,m=o*a*u,l=m-p,d=l-i,C=p+i,g=C>0?d/C*100:0,D=u>0?e*c/u+i/(a*u||1):e*c;return{buy_price:s(e,2),sell_price:s(o,2),quantity:s(a,8),buy_fee_percent:s(n,4),sell_fee_percent:s(r,4),network_fee:s(i,4),invested:s(p,2),returned:s(m,2),profit:s(d,2),profit_before_network_fee:s(l,2),roi_percent:y(g),break_even_price:s(D,2),is_profit:d>=0}}import{z as R}from"zod";var ve=R.object({coin:R.string(),date:R.string().optional().describe("Single date DD-MM-YYYY. Use with investment snapshot."),days:R.union([R.number(),R.string()]).optional().describe("Chart range: 1, 7, 14, 30, 90, 180, 365, or max"),vs_currency:R.string().default("usd"),investment:R.number().default(1e3).describe("Hypothetical investment amount on the historical date")});function Ve(t){let e=t.match(/^(\d{2})-(\d{2})-(\d{4})$/);if(!e)throw new Error(`Invalid date format: ${t}. Expected DD-MM-YYYY (e.g. 15-01-2024)`);let[,o,a,n]=e,r=`${o}-${a}-${n}`,i=`${n}-${a}-${o}`,c=new Date(i);if(isNaN(c.getTime()))throw new Error(`Invalid date: ${t}`);let u=new Date;if(u.setHours(23,59,59,999),c>u)throw new Error(`Date cannot be in the future: ${t}`);return{formatted:r,iso:i}}function Je(t){if(t.length===0)return null;let e=t.map(m=>m.price),o=e[0],a=e[e.length-1],n=Math.max(...e),r=Math.min(...e),i=a-o,c=o>0?i/o*100:0,u=Math.max(1,Math.floor(t.length/12)),p=t.filter((m,l)=>l%u===0||l===t.length-1).map(m=>({time:new Date(m.timestamp).toISOString(),price:s(m.price,h("usd",m.price))}));return{start_price:s(o,h("usd",o)),end_price:s(a,h("usd",a)),high:s(n,h("usd",n)),low:s(r,h("usd",r)),change:s(i,4),change_percent:y(c),points:t.length,sample:p}}async function Ce(t){let e=(t.vs_currency??"usd").toLowerCase(),o=t.investment??1e3,a=f(t.coin);if(!t.date&&!t.days)throw new Error("Provide either date (DD-MM-YYYY) for a point-in-time price, or days (e.g. 7, 30, 90) for a range chart.");if(t.days!==void 0&&t.days!==null){let l=await ce(t.coin,e,t.days),d=Je(l.prices);return{mode:"range",coin:a,vs_currency:e,days:String(t.days),summary:d}}let{formatted:n}=Ve(t.date),r=await ie(t.coin,n,e),i=await ue(t.coin,e),c=r.price>0?o/r.price:0,u=c*i,p=u-o,m=o>0?p/o*100:0;return{mode:"point",coin:a,date:n,vs_currency:e,historical_price:s(r.price,h(e,r.price)),current_price:s(i,h(e,i)),price_change:s(i-r.price,4),price_change_percent:y(r.price>0?(i-r.price)/r.price*100:0),investment_then:{invested:o,coins_bought:s(c,8),worth_now:s(u,2),gain_loss:s(p,2),gain_loss_percent:y(m)}}}import{z as X}from"zod";var Se=X.object({query:X.string().describe("Name or symbol to search, e.g. pepe, solana, wif"),limit:X.number().int().min(1).max(50).default(10)});async function Pe(t){let e=t.limit??10,o=await se(t.query,e);return{query:t.query,count:o.length,results:o}}import{z as U}from"zod";var xe=U.object({vs_currency:U.string().default("usd"),limit:U.number().int().min(1).max(50).default(10),order:U.enum(["market_cap_desc","market_cap_asc","volume_desc","volume_asc","id_asc","id_desc"]).default("market_cap_desc")});async function Re(t){let e=(t.vs_currency??"usd").toLowerCase(),o=t.limit??10,a=t.order??"market_cap_desc",r=(await F(e,{perPage:o,order:a})).map(p=>({id:p.id,symbol:p.symbol,name:p.name,rank:p.market_cap_rank,price:s(p.current_price??0,h(e,p.current_price??0)),market_cap:p.market_cap?s(p.market_cap,0):null,total_volume:p.total_volume?s(p.total_volume,0):null,"24h_change":p.price_change_percentage_24h?y(p.price_change_percentage_24h):null})),i=r.filter(p=>p["24h_change"]!==null),c=[...i].sort((p,m)=>(m["24h_change"]??0)-(p["24h_change"]??0)).slice(0,3),u=[...i].sort((p,m)=>(p["24h_change"]??0)-(m["24h_change"]??0)).slice(0,3);return{vs_currency:e,order:a,count:r.length,coins:r,top_gainers:c,top_losers:u}}import{z as G}from"zod";var Ie=G.object({coins:G.union([G.array(G.string()).min(2).max(5),G.string().describe("Comma-separated symbols, e.g. btc,eth,sol")]).describe("2\u20135 coins to compare"),vs_currency:G.string().default("usd")});function We(t){let e=Array.isArray(t)?t:t.split(/[,\s]+/).map(a=>a.trim()).filter(Boolean),o=[...new Set(e.map(a=>a.toLowerCase()))];if(o.length<2||o.length>5)throw new Error("Provide between 2 and 5 coins to compare");return o}async function Ee(t){let e=(t.vs_currency??"usd").toLowerCase(),o=We(t.coins),a=o.map(f),n=await F(e,{ids:a,perPage:a.length}),r=new Map(n.map(m=>[m.id,m])),i=a.map((m,l)=>{let d=r.get(m);return d?{input:o[l],id:d.id,found:!0,symbol:d.symbol,name:d.name,rank:d.market_cap_rank,price:s(d.current_price??0,h(e,d.current_price??0)),market_cap:d.market_cap?s(d.market_cap,0):null,total_volume:d.total_volume?s(d.total_volume,0):null,"24h_change":d.price_change_percentage_24h?y(d.price_change_percentage_24h):null}:{input:o[l],id:m,found:!1}}),c=i.filter(m=>m.found),u=[...c].sort((m,l)=>(l["24h_change"]??-1/0)-(m["24h_change"]??-1/0))[0],p=[...c].sort((m,l)=>(l.market_cap??0)-(m.market_cap??0))[0];return{vs_currency:e,coins:i,highlights:{best_24h_performer:u?.id??null,largest_market_cap:p?.id??null}}}import{z as $}from"zod";var Qe=$.object({coin:$.string(),amount:$.number().positive()}),Ae=$.object({holdings:$.array(Qe).min(1).max(50).describe("List of { coin, amount } holdings"),vs_currency:$.string().default("usd")});async function Te(t){let e=(t.vs_currency??"usd").toLowerCase(),o=t.holdings,a=o.map(l=>f(l.coin)),n=[...new Set(a)],r=e==="pkr"?"usd":e,i=await P(n,[r]),c=null,u=null;if(e==="pkr"){let l=await q(1,"usdt");c=l.rate,u=l.source}let p=o.map((l,d)=>{let C=a[d],g=i[C]?.[r];if(g===void 0)return{coin:C,amount:l.amount,found:!1,value:null};let D=l.amount*g,Y=g;return e==="pkr"&&c!==null&&(D*=c,Y*=c),{coin:C,amount:l.amount,found:!0,unit_price:s(Y,h(e,Y)),value:s(D,2)}}),m=p.reduce((l,d)=>l+(d.found&&d.value!==null?d.value:0),0);return{vs_currency:e,holdings:p,total_value:s(m,2),missing:p.filter(l=>!l.found).map(l=>l.coin),...u?{rate_source:u}:{}}}import{z as Ne}from"zod";var Ge=Ne.object({include_fear_greed:Ne.boolean().default(!0)});async function $e(t={include_fear_greed:!0}){let e=t.include_fear_greed??!0,[o,a]=await Promise.all([pe(),e?le():Promise.resolve(null)]);return{source:"coingecko",count:o.length,coins:o,...a?{fear_greed:{source:"alternative.me",value:a.value,classification:a.classification,updated_at:a.updatedAt}}:{}}}var rt=[{name:"get_price",description:"Get live price of any cryptocurrency in any fiat, with market cap, volume, rank, ATH/ATL. Uses CoinGecko.",inputSchema:{type:"object",properties:{coin:{type:"string",description:"Coin id or symbol: btc, bitcoin, eth, sol, etc"},vs_currency:{type:"string",description:"Fiat: usd, eur, pkr, inr, gbp, etc",default:"usd"}},required:["coin"]}},{name:"convert",description:"Convert crypto amount to fiat/crypto. Optimized PKR via Binance P2P when requested.",inputSchema:{type:"object",properties:{amount:{type:"number"},from:{type:"string"},to:{type:"string",description:"usd, eur, pkr, btc, eth, etc"}},required:["amount","from","to"]}},{name:"gas_tracker",description:"Live gas fees for EVM chains (eth, bnb, polygon, arbitrum, base, optimism) with estimated transfer cost in native + USD.",inputSchema:{type:"object",properties:{chain:{type:"string",enum:["eth","bnb","polygon","arbitrum","base","optimism"],default:"eth"}}}},{name:"profit_calc",description:"Calculate crypto profit/loss, ROI, break-even. Supports exchange fees and fixed network/gas fee. No API needed.",inputSchema:{type:"object",properties:{buy_price:{type:"number"},sell_price:{type:"number"},quantity:{type:"number"},buy_fee_percent:{type:"number",default:0},sell_fee_percent:{type:"number",default:0},network_fee:{type:"number",default:0,description:"Fixed network fee in same unit as prices"}},required:["buy_price","sell_price","quantity"]}},{name:"historical_price",description:"Historical price on a date (DD-MM-YYYY) with investment snapshot, or a range chart via days (1/7/30/90/365/max).",inputSchema:{type:"object",properties:{coin:{type:"string"},date:{type:"string",description:"DD-MM-YYYY for point-in-time"},days:{type:["string","number"],description:"Range chart: 1, 7, 14, 30, 90, 180, 365, or max"},vs_currency:{type:"string",default:"usd"},investment:{type:"number",default:1e3,description:"Hypothetical investment on the historical date"}},required:["coin"]}},{name:"search_coin",description:"Search cryptocurrencies by name or symbol. Returns CoinGecko ids for use in other tools.",inputSchema:{type:"object",properties:{query:{type:"string"},limit:{type:"number",default:10}},required:["query"]}},{name:"top_coins",description:"Top cryptocurrencies by market cap or volume, with gainers/losers among the result set.",inputSchema:{type:"object",properties:{vs_currency:{type:"string",default:"usd"},limit:{type:"number",default:10},order:{type:"string",enum:["market_cap_desc","market_cap_asc","volume_desc","volume_asc"],default:"market_cap_desc"}}}},{name:"compare_coins",description:"Compare 2\u20135 cryptocurrencies side by side (price, mcap, volume, 24h change).",inputSchema:{type:"object",properties:{coins:{oneOf:[{type:"string"},{type:"array",items:{type:"string"}}],description:"Array or comma-separated list, e.g. btc,eth,sol"},vs_currency:{type:"string",default:"usd"}},required:["coins"]}},{name:"portfolio_value",description:"Value a portfolio of holdings in any fiat (batch pricing). Pass [{coin, amount}, ...].",inputSchema:{type:"object",properties:{holdings:{type:"array",items:{type:"object",properties:{coin:{type:"string"},amount:{type:"number"}},required:["coin","amount"]}},vs_currency:{type:"string",default:"usd"}},required:["holdings"]}},{name:"trending",description:"What's hot right now: CoinGecko trending coins plus Crypto Fear & Greed Index.",inputSchema:{type:"object",properties:{include_fear_greed:{type:"boolean",default:!0,description:"Include Alternative.me Fear & Greed Index (default true)"}}}}],nt=[{name:"market-overview",description:"Global crypto market snapshot: top coins, gainers/losers, and trending + fear/greed",arguments:[{name:"vs_currency",description:"Fiat currency (default usd)",required:!1},{name:"limit",description:"How many top coins (default 10)",required:!1}]},{name:"whats-trending",description:"Trending coins and Fear & Greed Index snapshot",arguments:[]},{name:"analyze-coin",description:"Analyze a coin: live price, 7d history, and optional conversion",arguments:[{name:"coin",description:"Coin symbol or id",required:!0},{name:"vs_currency",description:"Fiat currency (default usd)",required:!1}]},{name:"trade-pnl",description:"Walk through a profit/loss calculation for a completed trade",arguments:[{name:"buy_price",description:"Entry price",required:!0},{name:"sell_price",description:"Exit price",required:!0},{name:"quantity",description:"Amount traded",required:!0},{name:"fees",description:"Optional fee notes (percent or network)",required:!1}]},{name:"gas-check",description:"Check current gas and estimated transfer cost for an EVM chain",arguments:[{name:"chain",description:"eth, bnb, polygon, arbitrum, base, or optimism",required:!1}]}];function Z(t){return{content:[{type:"text",text:t}],isError:!0}}function S(t){return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}function je(t){t.setRequestHandler(Ze,async()=>({tools:rt})),t.setRequestHandler(et,async()=>({prompts:nt})),t.setRequestHandler(tt,async e=>{let{name:o,arguments:a}=e.params,n=a??{};switch(o){case"market-overview":{let r=n.vs_currency??"usd",i=n.limit??"10";return{description:"Market overview workflow",messages:[{role:"user",content:{type:"text",text:`Give me a crypto market overview in ${r}. 1) Call top_coins with limit ${i}. 2) Call trending. Summarize leaders, gainers/losers, what's trending, and Fear & Greed tone.`}}]}}case"whats-trending":return{description:"Trending + sentiment workflow",messages:[{role:"user",content:{type:"text",text:"Call the trending tool. List the hottest coins and explain the Fear & Greed reading in plain language."}}]};case"analyze-coin":{let r=n.coin;if(!r)throw new Error("coin is required");let i=n.vs_currency??"usd";return{description:`Analyze ${r}`,messages:[{role:"user",content:{type:"text",text:`Analyze ${r} in ${i}. 1) Call get_price. 2) Call historical_price with days=7. 3) Summarize price, market cap, 24h move, and 7-day trend.`}}]}}case"trade-pnl":{let{buy_price:r,sell_price:i,quantity:c,fees:u}=n;if(!r||!i||!c)throw new Error("buy_price, sell_price, and quantity are required");return{description:"Trade P&L workflow",messages:[{role:"user",content:{type:"text",text:`Calculate P&L with profit_calc: buy_price=${r}, sell_price=${i}, quantity=${c}.${u?` Fee notes: ${u}.`:""} Explain profit, ROI, and break-even clearly.`}}]}}case"gas-check":{let r=n.chain??"eth";return{description:`Gas check for ${r}`,messages:[{role:"user",content:{type:"text",text:`Check gas on ${r} with gas_tracker. Report low/average/high gwei and estimated simple-transfer cost in USD. Advise whether fees look cheap or expensive.`}}]}}default:throw new Error(`Unknown prompt: ${o}`)}}),t.setRequestHandler(Xe,async e=>{let{name:o,arguments:a}=e.params;try{switch(o){case"get_price":return S(await he(de.parse(a??{})));case"convert":return S(await fe(ge.parse(a??{})));case"gas_tracker":return S(await ye(be.parse(a??{})));case"profit_calc":return S(ke(we.parse(a??{})));case"historical_price":return S(await Ce(ve.parse(a??{})));case"search_coin":return S(await Pe(Se.parse(a??{})));case"top_coins":return S(await Re(xe.parse(a??{})));case"compare_coins":return S(await Ee(Ie.parse(a??{})));case"portfolio_value":return S(await Te(Ae.parse(a??{})));case"trending":return S(await $e(Ge.parse(a??{})));default:return Z(`Unknown tool: ${o}`)}}catch(n){if(n instanceof w&&n.retryable)return Z(`Rate limit hit: ${n.message}. Wait a few seconds and retry.`);let r=n instanceof Error?n.message:"Unknown error occurred";return Z(r)}})}var Oe={name:"mcp-crypto-toolkit",version:"1.3.0",mcpName:"io.github.nad33mahm3d/mcp-crypto-toolkit",description:"Live crypto prices, conversion, gas tracker, portfolio tools and calculators for AI agents - MCP Server",type:"module",bin:{"mcp-crypto-toolkit":"dist/index.js"},files:["dist","README.md","LICENSE","CHANGELOG.md"],main:"./dist/index.js",exports:{".":"./dist/index.js"},scripts:{build:"tsup",dev:"tsup --watch",start:"node dist/index.js","start:stdio":"node dist/index.js","start:http":"node dist/http.js","dev:cf":"wrangler dev","deploy:cf":"wrangler deploy",test:"vitest run","test:watch":"vitest",inspector:"npx @modelcontextprotocol/inspector dist/index.js",prepack:"npm run build","pack:check":"npm pack --dry-run"},keywords:["mcp","mcp-server","crypto","bitcoin","ethereum","coingecko","gas","portfolio","claude","cursor","ai-agent"],license:"MIT",author:"Nadeem Ahmed <ebox.nadeem@gmail.com>",homepage:"https://github.com/nad33mahm3d/mcp-crypto-toolkit#readme",bugs:{url:"https://github.com/nad33mahm3d/mcp-crypto-toolkit/issues"},repository:{type:"git",url:"https://github.com/nad33mahm3d/mcp-crypto-toolkit.git"},publishConfig:{access:"public"},engines:{node:">=20"},dependencies:{"@modelcontextprotocol/sdk":"^1.0.0",zod:"^4.5.4"},devDependencies:{"@types/node":"^26.4.0",tsup:"^8.0.0",typescript:"^7.0.2",vitest:"^4.1.11",wrangler:"^4.129.0"}};var H=Oe.version,K="mcp-crypto-toolkit",Fe="Live crypto prices, conversion, gas tracker, portfolio tools and calculators for AI agents";function ee(){let t=new at({name:K,version:H,description:Fe},{capabilities:{tools:{},prompts:{}}});return je(t),t}var De=Number(process.env.PORT??3e3),j={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"GET, POST, OPTIONS","Access-Control-Allow-Headers":"content-type, accept, mcp-session-id, mcp-protocol-version","Access-Control-Expose-Headers":"mcp-session-id"};function it(t){return new Promise((e,o)=>{let a=[];t.on("data",n=>a.push(n)),t.on("end",()=>e(Buffer.concat(a).toString("utf8"))),t.on("error",o)})}function ct(t){if(!t||typeof t!="object")return!1;let e=t;return typeof e.method=="string"&&!("id"in e)}var ut=st(async(t,e)=>{try{let o=t.url?.split("?")[0]??"/";if(t.method==="OPTIONS"&&o.startsWith("/mcp")){e.writeHead(204,{...j,"Access-Control-Max-Age":"86400"}),e.end();return}if(t.method==="GET"&&(o==="/"||o==="/health")){let a=JSON.stringify({ok:!0,name:K,version:H,mcp:"/mcp"});e.writeHead(200,{"Content-Type":"application/json","Content-Length":Buffer.byteLength(a),...j}),e.end(a);return}if(o.startsWith("/mcp")){if(t.method==="POST"){let r=await it(t),i;try{i=r?JSON.parse(r):void 0}catch{let p=JSON.stringify({jsonrpc:"2.0",error:{code:-32700,message:"Parse error"},id:null});e.writeHead(400,{"Content-Type":"application/json","Content-Length":Buffer.byteLength(p),...j}),e.end(p);return}if(ct(i)){let p=`{"ok":true}
3
+ `;e.writeHead(202,{"Content-Type":"application/json","Content-Length":Buffer.byteLength(p),...j}),e.end(p);return}let c=ee(),u=new Me({sessionIdGenerator:void 0,enableJsonResponse:!0});e.on("close",()=>{u.close(),c.close()}),await c.connect(u),await u.handleRequest(t,e,i);return}let a=ee(),n=new Me({sessionIdGenerator:void 0,enableJsonResponse:!0});e.on("close",()=>{n.close(),a.close()}),await a.connect(n),await n.handleRequest(t,e);return}e.writeHead(404,{"Content-Type":"text/plain",...j}),e.end("Not found. Use POST /mcp or GET /health")}catch(o){console.error("HTTP error:",o),e.headersSent||(e.writeHead(500,{"Content-Type":"text/plain",...j}),e.end("Internal server error"))}});ut.listen(De,()=>{console.error(`mcp-crypto-toolkit HTTP listening on http://localhost:${De}/mcp`)});
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Server as He}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Be}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as Me,ListToolsRequestSchema as ze,ListPromptsRequestSchema as Le,GetPromptRequestSchema as Ue}from"@modelcontextprotocol/sdk/types.js";var Q={btc:"bitcoin",bitcoin:"bitcoin",eth:"ethereum",ethereum:"ethereum",usdt:"tether",tether:"tether",bnb:"binancecoin",binancecoin:"binancecoin",sol:"solana",solana:"solana",xrp:"ripple",ripple:"ripple",usdc:"usd-coin","usd-coin":"usd-coin",ada:"cardano",cardano:"cardano",avax:"avalanche-2",avalanche:"avalanche-2",doge:"dogecoin",dogecoin:"dogecoin",dot:"polkadot",polkadot:"polkadot",trx:"tron",tron:"tron",link:"chainlink",chainlink:"chainlink",matic:"matic-network",polygon:"matic-network","matic-network":"matic-network",shib:"shiba-inu","shiba-inu":"shiba-inu",ltc:"litecoin",litecoin:"litecoin",bch:"bitcoin-cash","bitcoin-cash":"bitcoin-cash",uni:"uniswap",uniswap:"uniswap",atom:"cosmos",cosmos:"cosmos",xlm:"stellar",stellar:"stellar",xmr:"monero",monero:"monero",etc:"ethereum-classic","ethereum-classic":"ethereum-classic",fil:"filecoin",filecoin:"filecoin",apt:"aptos",aptos:"aptos",arb:"arbitrum",arbitrum:"arbitrum",op:"optimism",optimism:"optimism",near:"near",vet:"vechain",vechain:"vechain",icp:"internet-computer",hbar:"hedera-hashgraph",algo:"algorand",algorand:"algorand",qnt:"quant-network",eos:"eos",aave:"aave",grt:"the-graph",sand:"the-sandbox",mana:"decentraland",axs:"axie-infinity",theta:"theta-token",ftm:"fantom",fantom:"fantom",xtz:"tezos",tezos:"tezos",rune:"thorchain",snx:"havven",crv:"curve-dao-token",mkr:"maker",maker:"maker",comp:"compound-governance-token",ldo:"lido-dao",sui:"sui",sei:"sei-network",inj:"injective-protocol",tia:"celestia",pepe:"pepe",wif:"dogwifcoin",bonk:"bonk",floki:"floki",render:"render-token",rndr:"render-token",fet:"fetch-ai",imx:"immutable-x",gala:"gala",enj:"enjincoin",bat:"basic-attention-token",zec:"zcash",dash:"dash",neo:"neo",kcs:"kucoin-shares",okb:"okb",ton:"the-open-network",kas:"kaspa",stx:"blockstack",rpl:"rocket-pool",blur:"blur",pendle:"pendle",jup:"jupiter-exchange-solana",pyth:"pyth-network",wld:"worldcoin-wld",strk:"starknet",ethfi:"ether-fi",ena:"ethena",ondo:"ondo-finance",not:"notcoin",bome:"book-of-meme",ar:"arweave",flow:"flow",mina:"mina-protocol",ro:"ronin",ronin:"ronin",egld:"elrond-erd-2",egld_m:"elrond-erd-2",cake:"pancakeswap-token","1inch":"1inch",ens:"ethereum-name-service",chz:"chiliz",hot:"holotoken",zil:"zilliqa",icx:"icon",waves:"waves",kava:"kava",celo:"celo",rose:"oasis-network",one:"harmony",iota:"iota",xem:"nem",sc:"siacoin",dcr:"decred",rvn:"ravencoin",zen:"zencash",ont:"ontology",qtum:"qtum",btt:"bittorrent",hnt:"helium",lrc:"loopring",ankr:"ankr",omg:"omisego",storj:"storj",skl:"skale",celr:"celer-network",coti:"coti",ren:"republic-protocol",band:"band-protocol",ocean:"ocean-protocol",api3:"api3",nmr:"numeraire",bal:"balancer",yfi:"yearn-finance",sushi:"sushi",dydx:"dydx-chain",gmx:"gmx",magic:"magic",rdnt:"radiant-capital",joe:"joe",woo:"woo-network",perp:"perpetual-protocol",lqty:"liquity",cvx:"convex-finance",frax:"frax",fxn:"fxn",usdd:"usdd",tusd:"true-usd",dai:"dai",frax_usd:"frax"};var X={eth:1,bnb:56,polygon:137,arbitrum:42161,base:8453,optimism:10},Z={eth:"ethereum",bnb:"binancecoin",polygon:"matic-network",arbitrum:"ethereum",base:"ethereum",optimism:"ethereum"},K=21e3,ee={eth:{url:"https://api.etherscan.io/api",apiKeyEnv:"ETHERSCAN_API_KEY"},bnb:{url:"https://api.bscscan.com/api",apiKeyEnv:"BSCSCAN_API_KEY"},polygon:{url:"https://api.polygonscan.com/api",apiKeyEnv:"POLYGONSCAN_API_KEY"},arbitrum:{url:"https://api.arbiscan.io/api",apiKeyEnv:"ARBISCAN_API_KEY"},base:{url:"https://api.basescan.org/api",apiKeyEnv:"BASESCAN_API_KEY"},optimism:{url:"https://api-optimistic.etherscan.io/api",apiKeyEnv:"OPTIMISM_API_KEY"}},R="mcp-crypto-toolkit/1.0.0 (https://github.com/nad33mahm3d/mcp-crypto-toolkit)";var E=class{store=new Map;timers=new Map;get(t){let o=this.store.get(t);if(o){if(Date.now()>o.expiresAt){this.delete(t);return}return o.value}}set(t,o,a){let n=this.timers.get(t);n&&clearTimeout(n),this.store.set(t,{value:o,expiresAt:Date.now()+a});let e=setTimeout(()=>{this.delete(t)},a);e.unref?.(),this.timers.set(t,e)}clear(){for(let t of this.timers.values())clearTimeout(t);this.timers.clear(),this.store.clear()}delete(t){let o=this.timers.get(t);o&&(clearTimeout(o),this.timers.delete(t)),this.store.delete(t)}},I=new E,Y=new E,N=new E,D=new E,k={PRICE:6e4,GAS:15e3,HISTORICAL:36e5,PKR:3e5,SEARCH:12e4};var G="https://api.coingecko.com/api/v3",b=class extends Error{constructor(o,a,n=!1){super(o);this.status=a;this.retryable=n;this.name="CoinGeckoError"}status;retryable};function f(r){let t=r.toLowerCase().trim();return Q[t]??t}function Ie(){let r=process.env.COINGECKO_API_KEY?.trim();return r?r.startsWith("CG-")&&process.env.COINGECKO_PRO==="1"?{"x-cg-pro-api-key":r}:{"x-cg-demo-api-key":r}:{}}async function q(r,t={},o=3){let a;for(let n=0;n<o;n++)try{let e=await fetch(r,{...t,headers:{Accept:"application/json","User-Agent":R,...Ie(),...t.headers??{}}});if(e.status===429){let i=Math.min(1e3*2**n,1e4);if(n<o-1){await te(i);continue}throw new b("CoinGecko rate limit exceeded (429). Wait a few seconds, or set COINGECKO_API_KEY for higher limits.",429,!0)}if(!e.ok)throw new b(`CoinGecko API error: ${e.status} ${e.statusText}`,e.status,e.status>=500);return e}catch(e){if(e instanceof b)throw e;a=e instanceof Error?e:new Error(String(e)),n<o-1&&await te(1e3*2**n)}throw a??new Error("Fetch failed after retries")}async function P(r,t){let o=[...new Set(r.map(f))],a=[...new Set(t.map(p=>p.toLowerCase()))],n=`simple:${o.sort().join(",")}:${a.sort().join(",")}`,e=I.get(n);if(e)return e;let i=new URLSearchParams({ids:o.join(","),vs_currencies:a.join(","),include_24hr_change:"true",include_last_updated_at:"true"}),u=await(await q(`${G}/simple/price?${i}`)).json();return I.set(n,u,k.PRICE),u}async function S(r,t){let o=f(r),a=t.toLowerCase(),e=(await P([o],[a]))[o];if(!e)throw new b(`Coin not found: ${r} (id: ${o})`);let i=e[a];if(i===void 0)throw new b(`Currency not supported: ${t}`);let c=`${a}_24h_change`,u="last_updated_at";return{id:o,price:Number(i),change24h:e[c]!==void 0?Number(e[c]):void 0,lastUpdated:e[u]!==void 0?new Date(Number(e[u])*1e3).toISOString():new Date().toISOString()}}async function j(r,t={}){let o=r.toLowerCase(),a=t.perPage??10,n=t.page??1,e=t.order??"market_cap_desc",i=t.ids?.map(f),c=`markets:${o}:${i?.sort().join(",")??"all"}:${a}:${n}:${e}`,u=I.get(c);if(u)return u;let p=new URLSearchParams({vs_currency:o,order:e,per_page:String(a),page:String(n),sparkline:"false",price_change_percentage:"24h"});i?.length&&p.set("ids",i.join(","));let l=await(await q(`${G}/coins/markets?${p}`)).json();return I.set(c,l,k.PRICE),l}async function H(r,t){let o=f(r),n=(await j(t,{ids:[o],perPage:1}))[0];if(!n)throw new b(`Coin not found: ${r} (id: ${o})`);return n}async function re(r,t=10){let o=r.trim();if(!o)return[];let a=`search:${o.toLowerCase()}:${t}`,n=I.get(a);if(n)return n;let c=((await(await q(`${G}/search?query=${encodeURIComponent(o)}`)).json()).coins??[]).slice(0,t).map(u=>({id:u.id,name:u.name,symbol:u.symbol,market_cap_rank:u.market_cap_rank}));return I.set(a,c,k.PRICE),c}async function ne(r,t,o){let a=f(r),n=o.toLowerCase(),e=`hist:${a}:${t}:${n}`,i=N.get(e);if(i)return i;let u=await(await q(`${G}/coins/${a}/history?date=${encodeURIComponent(t)}&localization=false`)).json();if(u.error||!u.market_data?.current_price?.[n])throw new b(u.error??`Historical price not found for ${r} on ${t}`);let p={id:a,date:t,price:u.market_data.current_price[n],vsCurrency:n};return N.set(e,p,k.HISTORICAL),p}async function oe(r,t,o){let a=f(r),n=t.toLowerCase(),e=String(o),i=`chart:${a}:${n}:${e}`,c=N.get(i);if(c)return c;let u=new URLSearchParams({vs_currency:n,days:e}),l=((await(await q(`${G}/coins/${a}/market_chart?${u}`)).json()).prices??[]).map(([v,h])=>({timestamp:v,price:h})),d={id:a,vs:n,days:e,prices:l};return N.set(i,d,k.HISTORICAL),d}async function ae(r,t){return(await S(r,t)).price}function te(r){return new Promise(t=>setTimeout(t,r))}function s(r,t=2){return Number.isFinite(r)?Number(r.toFixed(t)):0}function _(r,t=2){return Number.isFinite(r)?Number(r.toFixed(t)):0}function g(r,t){return r==="btc"||r==="eth"?8:t>=1?2:t>=.01?4:8}import{z as B}from"zod";async function F(){let r=D.get("usdt-pkr");if(r)return r;try{let o={rate:await Ae(),source:"binance_p2p",lastUpdated:new Date().toISOString()};return D.set("usdt-pkr",o,k.PKR),o}catch{let o={rate:await Ee(),source:"coingecko",lastUpdated:new Date().toISOString()};return D.set("usdt-pkr",o,k.PKR),o}}async function Ae(){let r=await fetch("https://p2p.binance.com/bapi/c2c/v2/friendly/c2c/adv/search",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json","User-Agent":R},body:JSON.stringify({asset:"USDT",fiat:"PKR",merchantCheck:!1,page:1,payTypes:[],publisherType:null,rows:10,tradeType:"BUY"})});if(!r.ok)throw new Error(`Binance P2P API error: ${r.status}`);let o=(await r.json()).data?.slice(0,5)??[];if(o.length===0)throw new Error("No Binance P2P ads found for USDT/PKR");let a=o.map(e=>parseFloat(e.adv.price)).filter(e=>!isNaN(e));if(a.length===0)throw new Error("Invalid Binance P2P price data");let n=a.reduce((e,i)=>e+i,0)/a.length;return Number(n.toFixed(2))}async function Ee(){let t=(await P(["tether"],["pkr"])).tether?.pkr;if(t===void 0)throw new b("Could not fetch USDT/PKR rate from CoinGecko");return Number(t.toFixed(2))}async function M(r,t){let o=t.toLowerCase();if(o==="pkr")return{converted:r,rate:1,source:"direct"};if(o==="usdt"||o==="tether"){let u=await F();return{converted:Number((r*u.rate).toFixed(2)),rate:u.rate,source:u.source}}let a=f(o),e=(await P([a],["usd"]))[a]?.usd;if(e===void 0)throw new b(`Could not get USD price for ${t}`);let i=r*e,c=await F();return{converted:Number((i*c.rate).toFixed(2)),rate:Number((e*c.rate).toFixed(2)),source:`coingecko_usd + ${c.source}`}}async function se(r,t){let o=t.toLowerCase();if(o==="pkr")return{converted:r,rate:1,source:"direct"};let a=await F(),n=r/a.rate;if(o==="usdt"||o==="tether"||o==="usd")return{converted:Number(n.toFixed(6)),rate:Number((1/a.rate).toFixed(8)),source:a.source};let e=f(o),c=(await P([e],["usd"]))[e]?.usd;if(!c)throw new b(`Could not get USD price for ${t}`);let u=n/c;return{converted:Number(u.toFixed(8)),rate:Number((a.rate/c).toFixed(2)),source:`coingecko_usd + ${a.source}`}}var ie=B.object({coin:B.string().describe("Coin id or symbol: btc, bitcoin, eth, sol, etc"),vs_currency:B.string().default("usd").describe("Fiat: usd, eur, pkr, inr, gbp, etc")});async function ce(r){let{coin:t,vs_currency:o}=r,a=o.toLowerCase();if(a==="pkr"){let n=await F(),e=await H(t,"usd"),i=(e.current_price??0)*n.rate;return{coin:e.id,symbol:e.symbol,name:e.name,price:s(i,2),vs_currency:"pkr","24h_change":e.price_change_percentage_24h?_(e.price_change_percentage_24h):null,market_cap:e.market_cap?s(e.market_cap*n.rate,0):null,market_cap_rank:e.market_cap_rank,total_volume:e.total_volume?s(e.total_volume*n.rate,0):null,high_24h:e.high_24h?s(e.high_24h*n.rate,2):null,low_24h:e.low_24h?s(e.low_24h*n.rate,2):null,ath:e.ath?s(e.ath*n.rate,2):null,ath_change_percent:e.ath_change_percentage?_(e.ath_change_percentage):null,usd_price:s(e.current_price,2),rate_source:n.source,last_updated:e.last_updated??new Date().toISOString()}}try{let n=await H(t,a),e=n.current_price??0;return{coin:n.id,symbol:n.symbol,name:n.name,price:s(e,g(a,e)),vs_currency:a,"24h_change":n.price_change_percentage_24h?_(n.price_change_percentage_24h):null,market_cap:n.market_cap?s(n.market_cap,0):null,market_cap_rank:n.market_cap_rank,total_volume:n.total_volume?s(n.total_volume,0):null,high_24h:n.high_24h?s(n.high_24h,g(a,n.high_24h)):null,low_24h:n.low_24h?s(n.low_24h,g(a,n.low_24h)):null,ath:n.ath?s(n.ath,g(a,n.ath)):null,ath_change_percent:n.ath_change_percentage?_(n.ath_change_percentage):null,atl:n.atl?s(n.atl,g(a,n.atl)):null,last_updated:n.last_updated??new Date().toISOString()}}catch{let n=await S(t,a);return{coin:n.id,price:s(n.price,g(a,n.price)),vs_currency:a,"24h_change":n.change24h?_(n.change24h):null,last_updated:n.lastUpdated??new Date().toISOString()}}}import{z}from"zod";var ue=z.object({amount:z.number(),from:z.string(),to:z.string().describe("usd, pkr, btc, eth, etc")});async function pe(r){let{amount:t,from:o,to:a}=r,n=o.toLowerCase(),e=a.toLowerCase();if(n===e)return{amount:t,from:n,to:e,converted:t,rate:1,source:"direct"};if(e==="pkr"){let h=await M(t,n);return{amount:t,from:f(n),to:"pkr",converted:h.converted,rate:h.rate,source:h.source}}if(n==="pkr"){let h=await se(t,e);return{amount:t,from:"pkr",to:f(e),converted:h.converted,rate:h.rate,source:h.source}}let i=f(n),c=f(e),u=new Set(["usd","eur","gbp","jpy","inr","aud","cad","chf","cny"]);if(u.has(e)){let h=await S(n,e);return{amount:t,from:i,to:e,converted:s(t*h.price,2),rate:s(h.price,2),source:"coingecko"}}if(u.has(n)){let h=await S(e,n);return{amount:t,from:n,to:c,converted:s(t/h.price,8),rate:s(1/h.price,8),source:"coingecko"}}let p=await P([i,c],["usd"]),m=p[i]?.usd,l=p[c]?.usd;if(!m||!l)throw new Error(`Could not convert ${o} to ${a}`);let d=m/l,v=t*d;return{amount:t,from:i,to:c,converted:s(v,8),rate:s(d,8),source:"coingecko"}}import{z as le}from"zod";var $e=["eth","bnb","polygon","arbitrum","base","optimism"],me=le.object({chain:le.enum($e).default("eth")});async function de(r){let t=r.chain,o=`gas:${t}`,a=Y.get(o);if(a)return a;let n,e=ee[t],i=e?process.env[e.apiKeyEnv]:void 0;if(e&&i)try{n=await Ne(t,e.url,i)}catch{}n||(n=await Ge(t));let c=await Te(n);return Y.set(o,c,k.GAS),c}function V(r){return r*K/1e9}async function Te(r){let t=Z[r.chain]??"ethereum",o=null;try{o=(await S(t,"usd")).price}catch{o=null}let a=V(r.low),n=V(r.average),e=V(r.high);return{...r,estimated_transfer:{gas_units:K,cost_native_low:s(a,8),cost_native_average:s(n,8),cost_native_high:s(e,8),cost_usd_low:o!==null?s(a*o,4):null,cost_usd_average:o!==null?s(n*o,4):null,cost_usd_high:o!==null?s(e*o,4):null,native_token:t}}}async function Ne(r,t,o){let a=`${t}?module=gastracker&action=gasoracle&apikey=${o}`,n=await fetch(a,{headers:{"User-Agent":R,Accept:"application/json"}});if(!n.ok)throw new Error(`Scan API error: ${n.status}`);let e=await n.json();if(e.status!=="1"||!e.result)throw new Error("Invalid scan gas oracle response");return{chain:r,low:s(parseFloat(e.result.SafeGasPrice??"0"),2),average:s(parseFloat(e.result.ProposeGasPrice??"0"),2),high:s(parseFloat(e.result.FastGasPrice??"0"),2),baseFee:e.result.suggestBaseFee?s(parseFloat(e.result.suggestBaseFee),2):null,unit:"gwei",last_updated:new Date().toISOString(),source:`${r}scan`}}async function Ge(r){let t=X[r];if(!t)throw new Error(`Unsupported chain: ${r}`);try{let o=await fetch(`https://api.blocknative.com/gasprices/blockprices?chainid=${t}`,{headers:{Accept:"application/json","User-Agent":R}});if(o.ok){let n=(await o.json()).blockPrices?.[0],e=n?.estimatedPrices??[];if(e.length>0){let i=[...e].sort((l,d)=>l.price-d.price),c=i[0]?.price??0,u=i[i.length-1]?.price??0,p=i[Math.floor(i.length/2)]?.price??c,m=n?.baseFeePerGas?n.baseFeePerGas/1e9:null;return{chain:r,low:s(c,2),average:s(p,2),high:s(u,2),baseFee:m!==null?s(m,2):null,unit:"gwei",last_updated:new Date().toISOString(),source:"blocknative"}}}}catch{}return qe(r)}function qe(r){let t={eth:{low:15,average:25,high:40},bnb:{low:3,average:5,high:8},polygon:{low:30,average:50,high:80},arbitrum:{low:.1,average:.2,high:.5},base:{low:.01,average:.05,high:.1},optimism:{low:.01,average:.05,high:.1}},o=t[r]??t.eth;return{chain:r,low:o.low,average:o.average,high:o.high,baseFee:null,unit:"gwei",last_updated:new Date().toISOString(),source:"static_estimate"}}import{z as A}from"zod";var he=A.object({buy_price:A.number(),sell_price:A.number(),quantity:A.number(),buy_fee_percent:A.number().default(0),sell_fee_percent:A.number().default(0),network_fee:A.number().default(0).describe("Fixed network/gas fee in the same unit as prices (e.g. USD)")});function ge(r){let t=r.buy_price,o=r.sell_price,a=r.quantity,n=r.buy_fee_percent??0,e=r.sell_fee_percent??0,i=r.network_fee??0,c=1+n/100,u=1-e/100,p=t*a*c,m=o*a*u,l=m-p,d=l-i,v=p+i,h=v>0?d/v*100:0,O=u>0?t*c/u+i/(a*u||1):t*c;return{buy_price:s(t,2),sell_price:s(o,2),quantity:s(a,8),buy_fee_percent:s(n,4),sell_fee_percent:s(e,4),network_fee:s(i,4),invested:s(p,2),returned:s(m,2),profit:s(d,2),profit_before_network_fee:s(l,2),roi_percent:_(h),break_even_price:s(O,2),is_profit:d>=0}}import{z as x}from"zod";var fe=x.object({coin:x.string(),date:x.string().optional().describe("Single date DD-MM-YYYY. Use with investment snapshot."),days:x.union([x.number(),x.string()]).optional().describe("Chart range: 1, 7, 14, 30, 90, 180, 365, or max"),vs_currency:x.string().default("usd"),investment:x.number().default(1e3).describe("Hypothetical investment amount on the historical date")});function je(r){let t=r.match(/^(\d{2})-(\d{2})-(\d{4})$/);if(!t)throw new Error(`Invalid date format: ${r}. Expected DD-MM-YYYY (e.g. 15-01-2024)`);let[,o,a,n]=t,e=`${o}-${a}-${n}`,i=`${n}-${a}-${o}`,c=new Date(i);if(isNaN(c.getTime()))throw new Error(`Invalid date: ${r}`);let u=new Date;if(u.setHours(23,59,59,999),c>u)throw new Error(`Date cannot be in the future: ${r}`);return{formatted:e,iso:i}}function Fe(r){if(r.length===0)return null;let t=r.map(m=>m.price),o=t[0],a=t[t.length-1],n=Math.max(...t),e=Math.min(...t),i=a-o,c=o>0?i/o*100:0,u=Math.max(1,Math.floor(r.length/12)),p=r.filter((m,l)=>l%u===0||l===r.length-1).map(m=>({time:new Date(m.timestamp).toISOString(),price:s(m.price,g("usd",m.price))}));return{start_price:s(o,g("usd",o)),end_price:s(a,g("usd",a)),high:s(n,g("usd",n)),low:s(e,g("usd",e)),change:s(i,4),change_percent:_(c),points:r.length,sample:p}}async function _e(r){let t=(r.vs_currency??"usd").toLowerCase(),o=r.investment??1e3,a=f(r.coin);if(!r.date&&!r.days)throw new Error("Provide either date (DD-MM-YYYY) for a point-in-time price, or days (e.g. 7, 30, 90) for a range chart.");if(r.days!==void 0&&r.days!==null){let l=await oe(r.coin,t,r.days),d=Fe(l.prices);return{mode:"range",coin:a,vs_currency:t,days:String(r.days),summary:d}}let{formatted:n}=je(r.date),e=await ne(r.coin,n,t),i=await ae(r.coin,t),c=e.price>0?o/e.price:0,u=c*i,p=u-o,m=o>0?p/o*100:0;return{mode:"point",coin:a,date:n,vs_currency:t,historical_price:s(e.price,g(t,e.price)),current_price:s(i,g(t,i)),price_change:s(i-e.price,4),price_change_percent:_(e.price>0?(i-e.price)/e.price*100:0),investment_then:{invested:o,coins_bought:s(c,8),worth_now:s(u,2),gain_loss:s(p,2),gain_loss_percent:_(m)}}}import{z as W}from"zod";var be=W.object({query:W.string().describe("Name or symbol to search, e.g. pepe, solana, wif"),limit:W.number().int().min(1).max(50).default(10)});async function ye(r){let t=r.limit??10,o=await re(r.query,t);return{query:r.query,count:o.length,results:o}}import{z as L}from"zod";var we=L.object({vs_currency:L.string().default("usd"),limit:L.number().int().min(1).max(50).default(10),order:L.enum(["market_cap_desc","market_cap_asc","volume_desc","volume_asc","id_asc","id_desc"]).default("market_cap_desc")});async function ke(r){let t=(r.vs_currency??"usd").toLowerCase(),o=r.limit??10,a=r.order??"market_cap_desc",e=(await j(t,{perPage:o,order:a})).map(p=>({id:p.id,symbol:p.symbol,name:p.name,rank:p.market_cap_rank,price:s(p.current_price??0,g(t,p.current_price??0)),market_cap:p.market_cap?s(p.market_cap,0):null,total_volume:p.total_volume?s(p.total_volume,0):null,"24h_change":p.price_change_percentage_24h?_(p.price_change_percentage_24h):null})),i=e.filter(p=>p["24h_change"]!==null),c=[...i].sort((p,m)=>(m["24h_change"]??0)-(p["24h_change"]??0)).slice(0,3),u=[...i].sort((p,m)=>(p["24h_change"]??0)-(m["24h_change"]??0)).slice(0,3);return{vs_currency:t,order:a,count:e.length,coins:e,top_gainers:c,top_losers:u}}import{z as $}from"zod";var ve=$.object({coins:$.union([$.array($.string()).min(2).max(5),$.string().describe("Comma-separated symbols, e.g. btc,eth,sol")]).describe("2\u20135 coins to compare"),vs_currency:$.string().default("usd")});function Oe(r){let t=Array.isArray(r)?r:r.split(/[,\s]+/).map(a=>a.trim()).filter(Boolean),o=[...new Set(t.map(a=>a.toLowerCase()))];if(o.length<2||o.length>5)throw new Error("Provide between 2 and 5 coins to compare");return o}async function Pe(r){let t=(r.vs_currency??"usd").toLowerCase(),o=Oe(r.coins),a=o.map(f),n=await j(t,{ids:a,perPage:a.length}),e=new Map(n.map(m=>[m.id,m])),i=a.map((m,l)=>{let d=e.get(m);return d?{input:o[l],id:d.id,found:!0,symbol:d.symbol,name:d.name,rank:d.market_cap_rank,price:s(d.current_price??0,g(t,d.current_price??0)),market_cap:d.market_cap?s(d.market_cap,0):null,total_volume:d.total_volume?s(d.total_volume,0):null,"24h_change":d.price_change_percentage_24h?_(d.price_change_percentage_24h):null}:{input:o[l],id:m,found:!1}}),c=i.filter(m=>m.found),u=[...c].sort((m,l)=>(l["24h_change"]??-1/0)-(m["24h_change"]??-1/0))[0],p=[...c].sort((m,l)=>(l.market_cap??0)-(m.market_cap??0))[0];return{vs_currency:t,coins:i,highlights:{best_24h_performer:u?.id??null,largest_market_cap:p?.id??null}}}import{z as T}from"zod";var De=T.object({coin:T.string(),amount:T.number().positive()}),Ce=T.object({holdings:T.array(De).min(1).max(50).describe("List of { coin, amount } holdings"),vs_currency:T.string().default("usd")});async function Se(r){let t=(r.vs_currency??"usd").toLowerCase(),o=r.holdings,a=o.map(l=>f(l.coin)),n=[...new Set(a)],e=t==="pkr"?"usd":t,i=await P(n,[e]),c=null,u=null;if(t==="pkr"){let l=await M(1,"usdt");c=l.rate,u=l.source}let p=o.map((l,d)=>{let v=a[d],h=i[v]?.[e];if(h===void 0)return{coin:v,amount:l.amount,found:!1,value:null};let O=l.amount*h,U=h;return t==="pkr"&&c!==null&&(O*=c,U*=c),{coin:v,amount:l.amount,found:!0,unit_price:s(U,g(t,U)),value:s(O,2)}}),m=p.reduce((l,d)=>l+(d.found&&d.value!==null?d.value:0),0);return{vs_currency:t,holdings:p,total_value:s(m,2),missing:p.filter(l=>!l.found).map(l=>l.coin),...u?{rate_source:u}:{}}}var Ke=[{name:"get_price",description:"Get live price of any cryptocurrency in any fiat, with market cap, volume, rank, ATH/ATL. Uses CoinGecko.",inputSchema:{type:"object",properties:{coin:{type:"string",description:"Coin id or symbol: btc, bitcoin, eth, sol, etc"},vs_currency:{type:"string",description:"Fiat: usd, eur, pkr, inr, gbp, etc",default:"usd"}},required:["coin"]}},{name:"convert",description:"Convert crypto amount to fiat/crypto. Optimized PKR via Binance P2P when requested.",inputSchema:{type:"object",properties:{amount:{type:"number"},from:{type:"string"},to:{type:"string",description:"usd, eur, pkr, btc, eth, etc"}},required:["amount","from","to"]}},{name:"gas_tracker",description:"Live gas fees for EVM chains (eth, bnb, polygon, arbitrum, base, optimism) with estimated transfer cost in native + USD.",inputSchema:{type:"object",properties:{chain:{type:"string",enum:["eth","bnb","polygon","arbitrum","base","optimism"],default:"eth"}}}},{name:"profit_calc",description:"Calculate crypto profit/loss, ROI, break-even. Supports exchange fees and fixed network/gas fee. No API needed.",inputSchema:{type:"object",properties:{buy_price:{type:"number"},sell_price:{type:"number"},quantity:{type:"number"},buy_fee_percent:{type:"number",default:0},sell_fee_percent:{type:"number",default:0},network_fee:{type:"number",default:0,description:"Fixed network fee in same unit as prices"}},required:["buy_price","sell_price","quantity"]}},{name:"historical_price",description:"Historical price on a date (DD-MM-YYYY) with investment snapshot, or a range chart via days (1/7/30/90/365/max).",inputSchema:{type:"object",properties:{coin:{type:"string"},date:{type:"string",description:"DD-MM-YYYY for point-in-time"},days:{type:["string","number"],description:"Range chart: 1, 7, 14, 30, 90, 180, 365, or max"},vs_currency:{type:"string",default:"usd"},investment:{type:"number",default:1e3,description:"Hypothetical investment on the historical date"}},required:["coin"]}},{name:"search_coin",description:"Search cryptocurrencies by name or symbol. Returns CoinGecko ids for use in other tools.",inputSchema:{type:"object",properties:{query:{type:"string"},limit:{type:"number",default:10}},required:["query"]}},{name:"top_coins",description:"Top cryptocurrencies by market cap or volume, with gainers/losers among the result set.",inputSchema:{type:"object",properties:{vs_currency:{type:"string",default:"usd"},limit:{type:"number",default:10},order:{type:"string",enum:["market_cap_desc","market_cap_asc","volume_desc","volume_asc"],default:"market_cap_desc"}}}},{name:"compare_coins",description:"Compare 2\u20135 cryptocurrencies side by side (price, mcap, volume, 24h change).",inputSchema:{type:"object",properties:{coins:{oneOf:[{type:"string"},{type:"array",items:{type:"string"}}],description:"Array or comma-separated list, e.g. btc,eth,sol"},vs_currency:{type:"string",default:"usd"}},required:["coins"]}},{name:"portfolio_value",description:"Value a portfolio of holdings in any fiat (batch pricing). Pass [{coin, amount}, ...].",inputSchema:{type:"object",properties:{holdings:{type:"array",items:{type:"object",properties:{coin:{type:"string"},amount:{type:"number"}},required:["coin","amount"]}},vs_currency:{type:"string",default:"usd"}},required:["holdings"]}}],Ye=[{name:"market-overview",description:"Global crypto market snapshot using top coins and gainers/losers",arguments:[{name:"vs_currency",description:"Fiat currency (default usd)",required:!1},{name:"limit",description:"How many top coins (default 10)",required:!1}]},{name:"analyze-coin",description:"Analyze a coin: live price, 7d history, and optional conversion",arguments:[{name:"coin",description:"Coin symbol or id",required:!0},{name:"vs_currency",description:"Fiat currency (default usd)",required:!1}]},{name:"trade-pnl",description:"Walk through a profit/loss calculation for a completed trade",arguments:[{name:"buy_price",description:"Entry price",required:!0},{name:"sell_price",description:"Exit price",required:!0},{name:"quantity",description:"Amount traded",required:!0},{name:"fees",description:"Optional fee notes (percent or network)",required:!1}]},{name:"gas-check",description:"Check current gas and estimated transfer cost for an EVM chain",arguments:[{name:"chain",description:"eth, bnb, polygon, arbitrum, base, or optimism",required:!1}]}];function J(r){return{content:[{type:"text",text:r}],isError:!0}}function C(r){return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}function xe(r){r.setRequestHandler(ze,async()=>({tools:Ke})),r.setRequestHandler(Le,async()=>({prompts:Ye})),r.setRequestHandler(Ue,async t=>{let{name:o,arguments:a}=t.params,n=a??{};switch(o){case"market-overview":{let e=n.vs_currency??"usd",i=n.limit??"10";return{description:"Market overview workflow",messages:[{role:"user",content:{type:"text",text:`Give me a crypto market overview in ${e}. Use the top_coins tool with limit ${i}. Summarize leaders, notable gainers/losers, and overall tone.`}}]}}case"analyze-coin":{let e=n.coin;if(!e)throw new Error("coin is required");let i=n.vs_currency??"usd";return{description:`Analyze ${e}`,messages:[{role:"user",content:{type:"text",text:`Analyze ${e} in ${i}. 1) Call get_price. 2) Call historical_price with days=7. 3) Summarize price, market cap, 24h move, and 7-day trend.`}}]}}case"trade-pnl":{let{buy_price:e,sell_price:i,quantity:c,fees:u}=n;if(!e||!i||!c)throw new Error("buy_price, sell_price, and quantity are required");return{description:"Trade P&L workflow",messages:[{role:"user",content:{type:"text",text:`Calculate P&L with profit_calc: buy_price=${e}, sell_price=${i}, quantity=${c}.${u?` Fee notes: ${u}.`:""} Explain profit, ROI, and break-even clearly.`}}]}}case"gas-check":{let e=n.chain??"eth";return{description:`Gas check for ${e}`,messages:[{role:"user",content:{type:"text",text:`Check gas on ${e} with gas_tracker. Report low/average/high gwei and estimated simple-transfer cost in USD. Advise whether fees look cheap or expensive.`}}]}}default:throw new Error(`Unknown prompt: ${o}`)}}),r.setRequestHandler(Me,async t=>{let{name:o,arguments:a}=t.params;try{switch(o){case"get_price":return C(await ce(ie.parse(a??{})));case"convert":return C(await pe(ue.parse(a??{})));case"gas_tracker":return C(await de(me.parse(a??{})));case"profit_calc":return C(ge(he.parse(a??{})));case"historical_price":return C(await _e(fe.parse(a??{})));case"search_coin":return C(await ye(be.parse(a??{})));case"top_coins":return C(await ke(we.parse(a??{})));case"compare_coins":return C(await Pe(ve.parse(a??{})));case"portfolio_value":return C(await Se(Ce.parse(a??{})));default:return J(`Unknown tool: ${o}`)}}catch(n){if(n instanceof b&&n.retryable)return J(`Rate limit hit: ${n.message}. Wait a few seconds and retry.`);let e=n instanceof Error?n.message:"Unknown error occurred";return J(e)}})}var Re=new He({name:"mcp-crypto-toolkit",version:"1.1.0",description:"Live crypto prices, conversion, gas tracker, portfolio tools and calculators for AI agents"},{capabilities:{tools:{},prompts:{}}});xe(Re);var Ve=new Be;await Re.connect(Ve);
2
+ import{StdioServerTransport as nt}from"@modelcontextprotocol/sdk/server/stdio.js";import{Server as rt}from"@modelcontextprotocol/sdk/server/index.js";import{CallToolRequestSchema as We,ListToolsRequestSchema as Je,ListPromptsRequestSchema as Qe,GetPromptRequestSchema as Xe}from"@modelcontextprotocol/sdk/types.js";var Q={btc:"bitcoin",bitcoin:"bitcoin",eth:"ethereum",ethereum:"ethereum",usdt:"tether",tether:"tether",bnb:"binancecoin",binancecoin:"binancecoin",sol:"solana",solana:"solana",xrp:"ripple",ripple:"ripple",usdc:"usd-coin","usd-coin":"usd-coin",ada:"cardano",cardano:"cardano",avax:"avalanche-2",avalanche:"avalanche-2",doge:"dogecoin",dogecoin:"dogecoin",dot:"polkadot",polkadot:"polkadot",trx:"tron",tron:"tron",link:"chainlink",chainlink:"chainlink",matic:"matic-network",polygon:"matic-network","matic-network":"matic-network",shib:"shiba-inu","shiba-inu":"shiba-inu",ltc:"litecoin",litecoin:"litecoin",bch:"bitcoin-cash","bitcoin-cash":"bitcoin-cash",uni:"uniswap",uniswap:"uniswap",atom:"cosmos",cosmos:"cosmos",xlm:"stellar",stellar:"stellar",xmr:"monero",monero:"monero",etc:"ethereum-classic","ethereum-classic":"ethereum-classic",fil:"filecoin",filecoin:"filecoin",apt:"aptos",aptos:"aptos",arb:"arbitrum",arbitrum:"arbitrum",op:"optimism",optimism:"optimism",near:"near",vet:"vechain",vechain:"vechain",icp:"internet-computer",hbar:"hedera-hashgraph",algo:"algorand",algorand:"algorand",qnt:"quant-network",eos:"eos",aave:"aave",grt:"the-graph",sand:"the-sandbox",mana:"decentraland",axs:"axie-infinity",theta:"theta-token",ftm:"fantom",fantom:"fantom",xtz:"tezos",tezos:"tezos",rune:"thorchain",snx:"havven",crv:"curve-dao-token",mkr:"maker",maker:"maker",comp:"compound-governance-token",ldo:"lido-dao",sui:"sui",sei:"sei-network",inj:"injective-protocol",tia:"celestia",pepe:"pepe",wif:"dogwifcoin",bonk:"bonk",floki:"floki",render:"render-token",rndr:"render-token",fet:"fetch-ai",imx:"immutable-x",gala:"gala",enj:"enjincoin",bat:"basic-attention-token",zec:"zcash",dash:"dash",neo:"neo",kcs:"kucoin-shares",okb:"okb",ton:"the-open-network",kas:"kaspa",stx:"blockstack",rpl:"rocket-pool",blur:"blur",pendle:"pendle",jup:"jupiter-exchange-solana",pyth:"pyth-network",wld:"worldcoin-wld",strk:"starknet",ethfi:"ether-fi",ena:"ethena",ondo:"ondo-finance",not:"notcoin",bome:"book-of-meme",ar:"arweave",flow:"flow",mina:"mina-protocol",ro:"ronin",ronin:"ronin",egld:"elrond-erd-2",egld_m:"elrond-erd-2",cake:"pancakeswap-token","1inch":"1inch",ens:"ethereum-name-service",chz:"chiliz",hot:"holotoken",zil:"zilliqa",icx:"icon",waves:"waves",kava:"kava",celo:"celo",rose:"oasis-network",one:"harmony",iota:"iota",xem:"nem",sc:"siacoin",dcr:"decred",rvn:"ravencoin",zen:"zencash",ont:"ontology",qtum:"qtum",btt:"bittorrent",hnt:"helium",lrc:"loopring",ankr:"ankr",omg:"omisego",storj:"storj",skl:"skale",celr:"celer-network",coti:"coti",ren:"republic-protocol",band:"band-protocol",ocean:"ocean-protocol",api3:"api3",nmr:"numeraire",bal:"balancer",yfi:"yearn-finance",sushi:"sushi",dydx:"dydx-chain",gmx:"gmx",magic:"magic",rdnt:"radiant-capital",joe:"joe",woo:"woo-network",perp:"perpetual-protocol",lqty:"liquity",cvx:"convex-finance",frax:"frax",fxn:"fxn",usdd:"usdd",tusd:"true-usd",dai:"dai",frax_usd:"frax"};var X={eth:1,bnb:56,polygon:137,arbitrum:42161,base:8453,optimism:10},Z={eth:"ethereum",bnb:"binancecoin",polygon:"matic-network",arbitrum:"ethereum",base:"ethereum",optimism:"ethereum"},K=21e3,ee={eth:{url:"https://api.etherscan.io/api",apiKeyEnv:"ETHERSCAN_API_KEY"},bnb:{url:"https://api.bscscan.com/api",apiKeyEnv:"BSCSCAN_API_KEY"},polygon:{url:"https://api.polygonscan.com/api",apiKeyEnv:"POLYGONSCAN_API_KEY"},arbitrum:{url:"https://api.arbiscan.io/api",apiKeyEnv:"ARBISCAN_API_KEY"},base:{url:"https://api.basescan.org/api",apiKeyEnv:"BASESCAN_API_KEY"},optimism:{url:"https://api-optimistic.etherscan.io/api",apiKeyEnv:"OPTIMISM_API_KEY"}},R="mcp-crypto-toolkit/1.0.0 (https://github.com/nad33mahm3d/mcp-crypto-toolkit)";var T=class{store=new Map;timers=new Map;get(r){let o=this.store.get(r);if(o){if(Date.now()>o.expiresAt){this.delete(r);return}return o.value}}set(r,o,a){let n=this.timers.get(r);n&&clearTimeout(n),this.store.set(r,{value:o,expiresAt:Date.now()+a});let e=setTimeout(()=>{this.delete(r)},a);e.unref?.(),this.timers.set(r,e)}clear(){for(let r of this.timers.values())clearTimeout(r);this.timers.clear(),this.store.clear()}delete(r){let o=this.timers.get(r);o&&(clearTimeout(o),this.timers.delete(r)),this.store.delete(r)}},v=new T,Y=new T,j=new T,O=new T,k={PRICE:6e4,GAS:15e3,HISTORICAL:36e5,PKR:3e5,SEARCH:12e4,TRENDING:18e4};var N="https://api.coingecko.com/api/v3",w=class extends Error{constructor(o,a,n=!1){super(o);this.status=a;this.retryable=n;this.name="CoinGeckoError"}status;retryable};function f(t){let r=t.toLowerCase().trim();return Q[r]??r}function qe(){let t=process.env.COINGECKO_API_KEY?.trim();return t?t.startsWith("CG-")&&process.env.COINGECKO_PRO==="1"?{"x-cg-pro-api-key":t}:{"x-cg-demo-api-key":t}:{}}async function E(t,r={},o=3){let a;for(let n=0;n<o;n++)try{let e=await fetch(t,{...r,headers:{Accept:"application/json","User-Agent":R,...qe(),...r.headers??{}}});if(e.status===429){let i=Math.min(1e3*2**n,1e4);if(n<o-1){await te(i);continue}throw new w("CoinGecko rate limit exceeded (429). Wait a few seconds, or set COINGECKO_API_KEY for higher limits.",429,!0)}if(!e.ok)throw new w(`CoinGecko API error: ${e.status} ${e.statusText}`,e.status,e.status>=500);return e}catch(e){if(e instanceof w)throw e;a=e instanceof Error?e:new Error(String(e)),n<o-1&&await te(1e3*2**n)}throw a??new Error("Fetch failed after retries")}async function S(t,r){let o=[...new Set(t.map(f))],a=[...new Set(r.map(p=>p.toLowerCase()))],n=`simple:${o.sort().join(",")}:${a.sort().join(",")}`,e=v.get(n);if(e)return e;let i=new URLSearchParams({ids:o.join(","),vs_currencies:a.join(","),include_24hr_change:"true",include_last_updated_at:"true"}),u=await(await E(`${N}/simple/price?${i}`)).json();return v.set(n,u,k.PRICE),u}async function P(t,r){let o=f(t),a=r.toLowerCase(),e=(await S([o],[a]))[o];if(!e)throw new w(`Coin not found: ${t} (id: ${o})`);let i=e[a];if(i===void 0)throw new w(`Currency not supported: ${r}`);let c=`${a}_24h_change`,u="last_updated_at";return{id:o,price:Number(i),change24h:e[c]!==void 0?Number(e[c]):void 0,lastUpdated:e[u]!==void 0?new Date(Number(e[u])*1e3).toISOString():new Date().toISOString()}}async function F(t,r={}){let o=t.toLowerCase(),a=r.perPage??10,n=r.page??1,e=r.order??"market_cap_desc",i=r.ids?.map(f),c=`markets:${o}:${i?.sort().join(",")??"all"}:${a}:${n}:${e}`,u=v.get(c);if(u)return u;let p=new URLSearchParams({vs_currency:o,order:e,per_page:String(a),page:String(n),sparkline:"false",price_change_percentage:"24h"});i?.length&&p.set("ids",i.join(","));let l=await(await E(`${N}/coins/markets?${p}`)).json();return v.set(c,l,k.PRICE),l}async function H(t,r){let o=f(t),n=(await F(r,{ids:[o],perPage:1}))[0];if(!n)throw new w(`Coin not found: ${t} (id: ${o})`);return n}async function re(t,r=10){let o=t.trim();if(!o)return[];let a=`search:${o.toLowerCase()}:${r}`,n=v.get(a);if(n)return n;let c=((await(await E(`${N}/search?query=${encodeURIComponent(o)}`)).json()).coins??[]).slice(0,r).map(u=>({id:u.id,name:u.name,symbol:u.symbol,market_cap_rank:u.market_cap_rank}));return v.set(a,c,k.PRICE),c}async function ne(t,r,o){let a=f(t),n=o.toLowerCase(),e=`hist:${a}:${r}:${n}`,i=j.get(e);if(i)return i;let u=await(await E(`${N}/coins/${a}/history?date=${encodeURIComponent(r)}&localization=false`)).json();if(u.error||!u.market_data?.current_price?.[n])throw new w(u.error??`Historical price not found for ${t} on ${r}`);let p={id:a,date:r,price:u.market_data.current_price[n],vsCurrency:n};return j.set(e,p,k.HISTORICAL),p}async function oe(t,r,o){let a=f(t),n=r.toLowerCase(),e=String(o),i=`chart:${a}:${n}:${e}`,c=j.get(i);if(c)return c;let u=new URLSearchParams({vs_currency:n,days:e}),l=((await(await E(`${N}/coins/${a}/market_chart?${u}`)).json()).prices??[]).map(([C,g])=>({timestamp:C,price:g})),d={id:a,vs:n,days:e,prices:l};return j.set(i,d,k.HISTORICAL),d}async function ae(t,r){return(await P(t,r)).price}async function se(){let t="trending:coins",r=v.get(t);if(r)return r;let n=((await(await E(`${N}/search/trending`)).json()).coins??[]).map(({item:e})=>({id:e.id,name:e.name,symbol:e.symbol,market_cap_rank:e.market_cap_rank,score:e.score,price_btc:e.price_btc??null,price_usd:typeof e.data?.price=="number"?s(e.data.price,h("usd",e.data.price)):null,change_24h_usd:typeof e.data?.price_change_percentage_24h?.usd=="number"?y(e.data.price_change_percentage_24h.usd):null}));return v.set(t,n,k.TRENDING),n}async function ie(){let t="fear-greed:1",r=v.get(t);if(r)return r;let n=(await(await E("https://api.alternative.me/fng/?limit=1")).json()).data?.[0];if(!n)throw new w("Fear & Greed index unavailable");let e={value:Number(n.value),classification:n.value_classification,updatedAt:new Date(Number(n.timestamp)*1e3).toISOString()};return v.set(t,e,k.TRENDING),e}function te(t){return new Promise(r=>setTimeout(r,t))}function s(t,r=2){return Number.isFinite(t)?Number(t.toFixed(r)):0}function y(t,r=2){return Number.isFinite(t)?Number(t.toFixed(r)):0}function h(t,r){return t==="btc"||t==="eth"?8:r>=1?2:r>=.01?4:8}import{z as V}from"zod";async function q(){let t=O.get("usdt-pkr");if(t)return t;try{let o={rate:await De(),source:"binance_p2p",lastUpdated:new Date().toISOString()};return O.set("usdt-pkr",o,k.PKR),o}catch{let o={rate:await Oe(),source:"coingecko",lastUpdated:new Date().toISOString()};return O.set("usdt-pkr",o,k.PKR),o}}async function De(){let t=await fetch("https://p2p.binance.com/bapi/c2c/v2/friendly/c2c/adv/search",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json","User-Agent":R},body:JSON.stringify({asset:"USDT",fiat:"PKR",merchantCheck:!1,page:1,payTypes:[],publisherType:null,rows:10,tradeType:"BUY"})});if(!t.ok)throw new Error(`Binance P2P API error: ${t.status}`);let o=(await t.json()).data?.slice(0,5)??[];if(o.length===0)throw new Error("No Binance P2P ads found for USDT/PKR");let a=o.map(e=>parseFloat(e.adv.price)).filter(e=>!isNaN(e));if(a.length===0)throw new Error("Invalid Binance P2P price data");let n=a.reduce((e,i)=>e+i,0)/a.length;return Number(n.toFixed(2))}async function Oe(){let r=(await S(["tether"],["pkr"])).tether?.pkr;if(r===void 0)throw new w("Could not fetch USDT/PKR rate from CoinGecko");return Number(r.toFixed(2))}async function M(t,r){let o=r.toLowerCase();if(o==="pkr")return{converted:t,rate:1,source:"direct"};if(o==="usdt"||o==="tether"){let u=await q();return{converted:Number((t*u.rate).toFixed(2)),rate:u.rate,source:u.source}}let a=f(o),e=(await S([a],["usd"]))[a]?.usd;if(e===void 0)throw new w(`Could not get USD price for ${r}`);let i=t*e,c=await q();return{converted:Number((i*c.rate).toFixed(2)),rate:Number((e*c.rate).toFixed(2)),source:`coingecko_usd + ${c.source}`}}async function ce(t,r){let o=r.toLowerCase();if(o==="pkr")return{converted:t,rate:1,source:"direct"};let a=await q(),n=t/a.rate;if(o==="usdt"||o==="tether"||o==="usd")return{converted:Number(n.toFixed(6)),rate:Number((1/a.rate).toFixed(8)),source:a.source};let e=f(o),c=(await S([e],["usd"]))[e]?.usd;if(!c)throw new w(`Could not get USD price for ${r}`);let u=n/c;return{converted:Number(u.toFixed(8)),rate:Number((a.rate/c).toFixed(2)),source:`coingecko_usd + ${a.source}`}}var ue=V.object({coin:V.string().describe("Coin id or symbol: btc, bitcoin, eth, sol, etc"),vs_currency:V.string().default("usd").describe("Fiat: usd, eur, pkr, inr, gbp, etc")});async function pe(t){let{coin:r,vs_currency:o}=t,a=o.toLowerCase();if(a==="pkr"){let n=await q(),e=await H(r,"usd"),i=(e.current_price??0)*n.rate;return{coin:e.id,symbol:e.symbol,name:e.name,price:s(i,2),vs_currency:"pkr","24h_change":e.price_change_percentage_24h?y(e.price_change_percentage_24h):null,market_cap:e.market_cap?s(e.market_cap*n.rate,0):null,market_cap_rank:e.market_cap_rank,total_volume:e.total_volume?s(e.total_volume*n.rate,0):null,high_24h:e.high_24h?s(e.high_24h*n.rate,2):null,low_24h:e.low_24h?s(e.low_24h*n.rate,2):null,ath:e.ath?s(e.ath*n.rate,2):null,ath_change_percent:e.ath_change_percentage?y(e.ath_change_percentage):null,usd_price:s(e.current_price,2),rate_source:n.source,last_updated:e.last_updated??new Date().toISOString()}}try{let n=await H(r,a),e=n.current_price??0;return{coin:n.id,symbol:n.symbol,name:n.name,price:s(e,h(a,e)),vs_currency:a,"24h_change":n.price_change_percentage_24h?y(n.price_change_percentage_24h):null,market_cap:n.market_cap?s(n.market_cap,0):null,market_cap_rank:n.market_cap_rank,total_volume:n.total_volume?s(n.total_volume,0):null,high_24h:n.high_24h?s(n.high_24h,h(a,n.high_24h)):null,low_24h:n.low_24h?s(n.low_24h,h(a,n.low_24h)):null,ath:n.ath?s(n.ath,h(a,n.ath)):null,ath_change_percent:n.ath_change_percentage?y(n.ath_change_percentage):null,atl:n.atl?s(n.atl,h(a,n.atl)):null,last_updated:n.last_updated??new Date().toISOString()}}catch{let n=await P(r,a);return{coin:n.id,price:s(n.price,h(a,n.price)),vs_currency:a,"24h_change":n.change24h?y(n.change24h):null,last_updated:n.lastUpdated??new Date().toISOString()}}}import{z}from"zod";var le=z.object({amount:z.number(),from:z.string(),to:z.string().describe("usd, pkr, btc, eth, etc")});async function me(t){let{amount:r,from:o,to:a}=t,n=o.toLowerCase(),e=a.toLowerCase();if(n===e)return{amount:r,from:n,to:e,converted:r,rate:1,source:"direct"};if(e==="pkr"){let g=await M(r,n);return{amount:r,from:f(n),to:"pkr",converted:g.converted,rate:g.rate,source:g.source}}if(n==="pkr"){let g=await ce(r,e);return{amount:r,from:"pkr",to:f(e),converted:g.converted,rate:g.rate,source:g.source}}let i=f(n),c=f(e),u=new Set(["usd","eur","gbp","jpy","inr","aud","cad","chf","cny"]);if(u.has(e)){let g=await P(n,e);return{amount:r,from:i,to:e,converted:s(r*g.price,2),rate:s(g.price,2),source:"coingecko"}}if(u.has(n)){let g=await P(e,n);return{amount:r,from:n,to:c,converted:s(r/g.price,8),rate:s(1/g.price,8),source:"coingecko"}}let p=await S([i,c],["usd"]),m=p[i]?.usd,l=p[c]?.usd;if(!m||!l)throw new Error(`Could not convert ${o} to ${a}`);let d=m/l,C=r*d;return{amount:r,from:i,to:c,converted:s(C,8),rate:s(d,8),source:"coingecko"}}import{z as de}from"zod";var Me=["eth","bnb","polygon","arbitrum","base","optimism"],he=de.object({chain:de.enum(Me).default("eth")});async function ge(t){let r=t.chain,o=`gas:${r}`,a=Y.get(o);if(a)return a;let n,e=ee[r],i=e?process.env[e.apiKeyEnv]:void 0;if(e&&i)try{n=await Le(r,e.url,i)}catch{}n||(n=await Ue(r));let c=await ze(n);return Y.set(o,c,k.GAS),c}function B(t){return t*K/1e9}async function ze(t){let r=Z[t.chain]??"ethereum",o=null;try{o=(await P(r,"usd")).price}catch{o=null}let a=B(t.low),n=B(t.average),e=B(t.high);return{...t,estimated_transfer:{gas_units:K,cost_native_low:s(a,8),cost_native_average:s(n,8),cost_native_high:s(e,8),cost_usd_low:o!==null?s(a*o,4):null,cost_usd_average:o!==null?s(n*o,4):null,cost_usd_high:o!==null?s(e*o,4):null,native_token:r}}}async function Le(t,r,o){let a=`${r}?module=gastracker&action=gasoracle&apikey=${o}`,n=await fetch(a,{headers:{"User-Agent":R,Accept:"application/json"}});if(!n.ok)throw new Error(`Scan API error: ${n.status}`);let e=await n.json();if(e.status!=="1"||!e.result)throw new Error("Invalid scan gas oracle response");return{chain:t,low:s(parseFloat(e.result.SafeGasPrice??"0"),2),average:s(parseFloat(e.result.ProposeGasPrice??"0"),2),high:s(parseFloat(e.result.FastGasPrice??"0"),2),baseFee:e.result.suggestBaseFee?s(parseFloat(e.result.suggestBaseFee),2):null,unit:"gwei",last_updated:new Date().toISOString(),source:`${t}scan`}}async function Ue(t){let r=X[t];if(!r)throw new Error(`Unsupported chain: ${t}`);try{let o=await fetch(`https://api.blocknative.com/gasprices/blockprices?chainid=${r}`,{headers:{Accept:"application/json","User-Agent":R}});if(o.ok){let n=(await o.json()).blockPrices?.[0],e=n?.estimatedPrices??[];if(e.length>0){let i=[...e].sort((l,d)=>l.price-d.price),c=i[0]?.price??0,u=i[i.length-1]?.price??0,p=i[Math.floor(i.length/2)]?.price??c,m=n?.baseFeePerGas?n.baseFeePerGas/1e9:null;return{chain:t,low:s(c,2),average:s(p,2),high:s(u,2),baseFee:m!==null?s(m,2):null,unit:"gwei",last_updated:new Date().toISOString(),source:"blocknative"}}}}catch{}return Ke(t)}function Ke(t){let r={eth:{low:15,average:25,high:40},bnb:{low:3,average:5,high:8},polygon:{low:30,average:50,high:80},arbitrum:{low:.1,average:.2,high:.5},base:{low:.01,average:.05,high:.1},optimism:{low:.01,average:.05,high:.1}},o=r[t]??r.eth;return{chain:t,low:o.low,average:o.average,high:o.high,baseFee:null,unit:"gwei",last_updated:new Date().toISOString(),source:"static_estimate"}}import{z as A}from"zod";var fe=A.object({buy_price:A.number(),sell_price:A.number(),quantity:A.number(),buy_fee_percent:A.number().default(0),sell_fee_percent:A.number().default(0),network_fee:A.number().default(0).describe("Fixed network/gas fee in the same unit as prices (e.g. USD)")});function _e(t){let r=t.buy_price,o=t.sell_price,a=t.quantity,n=t.buy_fee_percent??0,e=t.sell_fee_percent??0,i=t.network_fee??0,c=1+n/100,u=1-e/100,p=r*a*c,m=o*a*u,l=m-p,d=l-i,C=p+i,g=C>0?d/C*100:0,D=u>0?r*c/u+i/(a*u||1):r*c;return{buy_price:s(r,2),sell_price:s(o,2),quantity:s(a,8),buy_fee_percent:s(n,4),sell_fee_percent:s(e,4),network_fee:s(i,4),invested:s(p,2),returned:s(m,2),profit:s(d,2),profit_before_network_fee:s(l,2),roi_percent:y(g),break_even_price:s(D,2),is_profit:d>=0}}import{z as I}from"zod";var be=I.object({coin:I.string(),date:I.string().optional().describe("Single date DD-MM-YYYY. Use with investment snapshot."),days:I.union([I.number(),I.string()]).optional().describe("Chart range: 1, 7, 14, 30, 90, 180, 365, or max"),vs_currency:I.string().default("usd"),investment:I.number().default(1e3).describe("Hypothetical investment amount on the historical date")});function Ye(t){let r=t.match(/^(\d{2})-(\d{2})-(\d{4})$/);if(!r)throw new Error(`Invalid date format: ${t}. Expected DD-MM-YYYY (e.g. 15-01-2024)`);let[,o,a,n]=r,e=`${o}-${a}-${n}`,i=`${n}-${a}-${o}`,c=new Date(i);if(isNaN(c.getTime()))throw new Error(`Invalid date: ${t}`);let u=new Date;if(u.setHours(23,59,59,999),c>u)throw new Error(`Date cannot be in the future: ${t}`);return{formatted:e,iso:i}}function He(t){if(t.length===0)return null;let r=t.map(m=>m.price),o=r[0],a=r[r.length-1],n=Math.max(...r),e=Math.min(...r),i=a-o,c=o>0?i/o*100:0,u=Math.max(1,Math.floor(t.length/12)),p=t.filter((m,l)=>l%u===0||l===t.length-1).map(m=>({time:new Date(m.timestamp).toISOString(),price:s(m.price,h("usd",m.price))}));return{start_price:s(o,h("usd",o)),end_price:s(a,h("usd",a)),high:s(n,h("usd",n)),low:s(e,h("usd",e)),change:s(i,4),change_percent:y(c),points:t.length,sample:p}}async function ye(t){let r=(t.vs_currency??"usd").toLowerCase(),o=t.investment??1e3,a=f(t.coin);if(!t.date&&!t.days)throw new Error("Provide either date (DD-MM-YYYY) for a point-in-time price, or days (e.g. 7, 30, 90) for a range chart.");if(t.days!==void 0&&t.days!==null){let l=await oe(t.coin,r,t.days),d=He(l.prices);return{mode:"range",coin:a,vs_currency:r,days:String(t.days),summary:d}}let{formatted:n}=Ye(t.date),e=await ne(t.coin,n,r),i=await ae(t.coin,r),c=e.price>0?o/e.price:0,u=c*i,p=u-o,m=o>0?p/o*100:0;return{mode:"point",coin:a,date:n,vs_currency:r,historical_price:s(e.price,h(r,e.price)),current_price:s(i,h(r,i)),price_change:s(i-e.price,4),price_change_percent:y(e.price>0?(i-e.price)/e.price*100:0),investment_then:{invested:o,coins_bought:s(c,8),worth_now:s(u,2),gain_loss:s(p,2),gain_loss_percent:y(m)}}}import{z as W}from"zod";var we=W.object({query:W.string().describe("Name or symbol to search, e.g. pepe, solana, wif"),limit:W.number().int().min(1).max(50).default(10)});async function ke(t){let r=t.limit??10,o=await re(t.query,r);return{query:t.query,count:o.length,results:o}}import{z as L}from"zod";var ve=L.object({vs_currency:L.string().default("usd"),limit:L.number().int().min(1).max(50).default(10),order:L.enum(["market_cap_desc","market_cap_asc","volume_desc","volume_asc","id_asc","id_desc"]).default("market_cap_desc")});async function Ce(t){let r=(t.vs_currency??"usd").toLowerCase(),o=t.limit??10,a=t.order??"market_cap_desc",e=(await F(r,{perPage:o,order:a})).map(p=>({id:p.id,symbol:p.symbol,name:p.name,rank:p.market_cap_rank,price:s(p.current_price??0,h(r,p.current_price??0)),market_cap:p.market_cap?s(p.market_cap,0):null,total_volume:p.total_volume?s(p.total_volume,0):null,"24h_change":p.price_change_percentage_24h?y(p.price_change_percentage_24h):null})),i=e.filter(p=>p["24h_change"]!==null),c=[...i].sort((p,m)=>(m["24h_change"]??0)-(p["24h_change"]??0)).slice(0,3),u=[...i].sort((p,m)=>(p["24h_change"]??0)-(m["24h_change"]??0)).slice(0,3);return{vs_currency:r,order:a,count:e.length,coins:e,top_gainers:c,top_losers:u}}import{z as G}from"zod";var xe=G.object({coins:G.union([G.array(G.string()).min(2).max(5),G.string().describe("Comma-separated symbols, e.g. btc,eth,sol")]).describe("2\u20135 coins to compare"),vs_currency:G.string().default("usd")});function Ve(t){let r=Array.isArray(t)?t:t.split(/[,\s]+/).map(a=>a.trim()).filter(Boolean),o=[...new Set(r.map(a=>a.toLowerCase()))];if(o.length<2||o.length>5)throw new Error("Provide between 2 and 5 coins to compare");return o}async function Se(t){let r=(t.vs_currency??"usd").toLowerCase(),o=Ve(t.coins),a=o.map(f),n=await F(r,{ids:a,perPage:a.length}),e=new Map(n.map(m=>[m.id,m])),i=a.map((m,l)=>{let d=e.get(m);return d?{input:o[l],id:d.id,found:!0,symbol:d.symbol,name:d.name,rank:d.market_cap_rank,price:s(d.current_price??0,h(r,d.current_price??0)),market_cap:d.market_cap?s(d.market_cap,0):null,total_volume:d.total_volume?s(d.total_volume,0):null,"24h_change":d.price_change_percentage_24h?y(d.price_change_percentage_24h):null}:{input:o[l],id:m,found:!1}}),c=i.filter(m=>m.found),u=[...c].sort((m,l)=>(l["24h_change"]??-1/0)-(m["24h_change"]??-1/0))[0],p=[...c].sort((m,l)=>(l.market_cap??0)-(m.market_cap??0))[0];return{vs_currency:r,coins:i,highlights:{best_24h_performer:u?.id??null,largest_market_cap:p?.id??null}}}import{z as $}from"zod";var Be=$.object({coin:$.string(),amount:$.number().positive()}),Pe=$.object({holdings:$.array(Be).min(1).max(50).describe("List of { coin, amount } holdings"),vs_currency:$.string().default("usd")});async function Ie(t){let r=(t.vs_currency??"usd").toLowerCase(),o=t.holdings,a=o.map(l=>f(l.coin)),n=[...new Set(a)],e=r==="pkr"?"usd":r,i=await S(n,[e]),c=null,u=null;if(r==="pkr"){let l=await M(1,"usdt");c=l.rate,u=l.source}let p=o.map((l,d)=>{let C=a[d],g=i[C]?.[e];if(g===void 0)return{coin:C,amount:l.amount,found:!1,value:null};let D=l.amount*g,U=g;return r==="pkr"&&c!==null&&(D*=c,U*=c),{coin:C,amount:l.amount,found:!0,unit_price:s(U,h(r,U)),value:s(D,2)}}),m=p.reduce((l,d)=>l+(d.found&&d.value!==null?d.value:0),0);return{vs_currency:r,holdings:p,total_value:s(m,2),missing:p.filter(l=>!l.found).map(l=>l.coin),...u?{rate_source:u}:{}}}import{z as Re}from"zod";var Ee=Re.object({include_fear_greed:Re.boolean().default(!0)});async function Ae(t={include_fear_greed:!0}){let r=t.include_fear_greed??!0,[o,a]=await Promise.all([se(),r?ie():Promise.resolve(null)]);return{source:"coingecko",count:o.length,coins:o,...a?{fear_greed:{source:"alternative.me",value:a.value,classification:a.classification,updated_at:a.updatedAt}}:{}}}var Ze=[{name:"get_price",description:"Get live price of any cryptocurrency in any fiat, with market cap, volume, rank, ATH/ATL. Uses CoinGecko.",inputSchema:{type:"object",properties:{coin:{type:"string",description:"Coin id or symbol: btc, bitcoin, eth, sol, etc"},vs_currency:{type:"string",description:"Fiat: usd, eur, pkr, inr, gbp, etc",default:"usd"}},required:["coin"]}},{name:"convert",description:"Convert crypto amount to fiat/crypto. Optimized PKR via Binance P2P when requested.",inputSchema:{type:"object",properties:{amount:{type:"number"},from:{type:"string"},to:{type:"string",description:"usd, eur, pkr, btc, eth, etc"}},required:["amount","from","to"]}},{name:"gas_tracker",description:"Live gas fees for EVM chains (eth, bnb, polygon, arbitrum, base, optimism) with estimated transfer cost in native + USD.",inputSchema:{type:"object",properties:{chain:{type:"string",enum:["eth","bnb","polygon","arbitrum","base","optimism"],default:"eth"}}}},{name:"profit_calc",description:"Calculate crypto profit/loss, ROI, break-even. Supports exchange fees and fixed network/gas fee. No API needed.",inputSchema:{type:"object",properties:{buy_price:{type:"number"},sell_price:{type:"number"},quantity:{type:"number"},buy_fee_percent:{type:"number",default:0},sell_fee_percent:{type:"number",default:0},network_fee:{type:"number",default:0,description:"Fixed network fee in same unit as prices"}},required:["buy_price","sell_price","quantity"]}},{name:"historical_price",description:"Historical price on a date (DD-MM-YYYY) with investment snapshot, or a range chart via days (1/7/30/90/365/max).",inputSchema:{type:"object",properties:{coin:{type:"string"},date:{type:"string",description:"DD-MM-YYYY for point-in-time"},days:{type:["string","number"],description:"Range chart: 1, 7, 14, 30, 90, 180, 365, or max"},vs_currency:{type:"string",default:"usd"},investment:{type:"number",default:1e3,description:"Hypothetical investment on the historical date"}},required:["coin"]}},{name:"search_coin",description:"Search cryptocurrencies by name or symbol. Returns CoinGecko ids for use in other tools.",inputSchema:{type:"object",properties:{query:{type:"string"},limit:{type:"number",default:10}},required:["query"]}},{name:"top_coins",description:"Top cryptocurrencies by market cap or volume, with gainers/losers among the result set.",inputSchema:{type:"object",properties:{vs_currency:{type:"string",default:"usd"},limit:{type:"number",default:10},order:{type:"string",enum:["market_cap_desc","market_cap_asc","volume_desc","volume_asc"],default:"market_cap_desc"}}}},{name:"compare_coins",description:"Compare 2\u20135 cryptocurrencies side by side (price, mcap, volume, 24h change).",inputSchema:{type:"object",properties:{coins:{oneOf:[{type:"string"},{type:"array",items:{type:"string"}}],description:"Array or comma-separated list, e.g. btc,eth,sol"},vs_currency:{type:"string",default:"usd"}},required:["coins"]}},{name:"portfolio_value",description:"Value a portfolio of holdings in any fiat (batch pricing). Pass [{coin, amount}, ...].",inputSchema:{type:"object",properties:{holdings:{type:"array",items:{type:"object",properties:{coin:{type:"string"},amount:{type:"number"}},required:["coin","amount"]}},vs_currency:{type:"string",default:"usd"}},required:["holdings"]}},{name:"trending",description:"What's hot right now: CoinGecko trending coins plus Crypto Fear & Greed Index.",inputSchema:{type:"object",properties:{include_fear_greed:{type:"boolean",default:!0,description:"Include Alternative.me Fear & Greed Index (default true)"}}}}],et=[{name:"market-overview",description:"Global crypto market snapshot: top coins, gainers/losers, and trending + fear/greed",arguments:[{name:"vs_currency",description:"Fiat currency (default usd)",required:!1},{name:"limit",description:"How many top coins (default 10)",required:!1}]},{name:"whats-trending",description:"Trending coins and Fear & Greed Index snapshot",arguments:[]},{name:"analyze-coin",description:"Analyze a coin: live price, 7d history, and optional conversion",arguments:[{name:"coin",description:"Coin symbol or id",required:!0},{name:"vs_currency",description:"Fiat currency (default usd)",required:!1}]},{name:"trade-pnl",description:"Walk through a profit/loss calculation for a completed trade",arguments:[{name:"buy_price",description:"Entry price",required:!0},{name:"sell_price",description:"Exit price",required:!0},{name:"quantity",description:"Amount traded",required:!0},{name:"fees",description:"Optional fee notes (percent or network)",required:!1}]},{name:"gas-check",description:"Check current gas and estimated transfer cost for an EVM chain",arguments:[{name:"chain",description:"eth, bnb, polygon, arbitrum, base, or optimism",required:!1}]}];function J(t){return{content:[{type:"text",text:t}],isError:!0}}function x(t){return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}function Te(t){t.setRequestHandler(Je,async()=>({tools:Ze})),t.setRequestHandler(Qe,async()=>({prompts:et})),t.setRequestHandler(Xe,async r=>{let{name:o,arguments:a}=r.params,n=a??{};switch(o){case"market-overview":{let e=n.vs_currency??"usd",i=n.limit??"10";return{description:"Market overview workflow",messages:[{role:"user",content:{type:"text",text:`Give me a crypto market overview in ${e}. 1) Call top_coins with limit ${i}. 2) Call trending. Summarize leaders, gainers/losers, what's trending, and Fear & Greed tone.`}}]}}case"whats-trending":return{description:"Trending + sentiment workflow",messages:[{role:"user",content:{type:"text",text:"Call the trending tool. List the hottest coins and explain the Fear & Greed reading in plain language."}}]};case"analyze-coin":{let e=n.coin;if(!e)throw new Error("coin is required");let i=n.vs_currency??"usd";return{description:`Analyze ${e}`,messages:[{role:"user",content:{type:"text",text:`Analyze ${e} in ${i}. 1) Call get_price. 2) Call historical_price with days=7. 3) Summarize price, market cap, 24h move, and 7-day trend.`}}]}}case"trade-pnl":{let{buy_price:e,sell_price:i,quantity:c,fees:u}=n;if(!e||!i||!c)throw new Error("buy_price, sell_price, and quantity are required");return{description:"Trade P&L workflow",messages:[{role:"user",content:{type:"text",text:`Calculate P&L with profit_calc: buy_price=${e}, sell_price=${i}, quantity=${c}.${u?` Fee notes: ${u}.`:""} Explain profit, ROI, and break-even clearly.`}}]}}case"gas-check":{let e=n.chain??"eth";return{description:`Gas check for ${e}`,messages:[{role:"user",content:{type:"text",text:`Check gas on ${e} with gas_tracker. Report low/average/high gwei and estimated simple-transfer cost in USD. Advise whether fees look cheap or expensive.`}}]}}default:throw new Error(`Unknown prompt: ${o}`)}}),t.setRequestHandler(We,async r=>{let{name:o,arguments:a}=r.params;try{switch(o){case"get_price":return x(await pe(ue.parse(a??{})));case"convert":return x(await me(le.parse(a??{})));case"gas_tracker":return x(await ge(he.parse(a??{})));case"profit_calc":return x(_e(fe.parse(a??{})));case"historical_price":return x(await ye(be.parse(a??{})));case"search_coin":return x(await ke(we.parse(a??{})));case"top_coins":return x(await Ce(ve.parse(a??{})));case"compare_coins":return x(await Se(xe.parse(a??{})));case"portfolio_value":return x(await Ie(Pe.parse(a??{})));case"trending":return x(await Ae(Ee.parse(a??{})));default:return J(`Unknown tool: ${o}`)}}catch(n){if(n instanceof w&&n.retryable)return J(`Rate limit hit: ${n.message}. Wait a few seconds and retry.`);let e=n instanceof Error?n.message:"Unknown error occurred";return J(e)}})}var Ne={name:"mcp-crypto-toolkit",version:"1.3.0",mcpName:"io.github.nad33mahm3d/mcp-crypto-toolkit",description:"Live crypto prices, conversion, gas tracker, portfolio tools and calculators for AI agents - MCP Server",type:"module",bin:{"mcp-crypto-toolkit":"dist/index.js"},files:["dist","README.md","LICENSE","CHANGELOG.md"],main:"./dist/index.js",exports:{".":"./dist/index.js"},scripts:{build:"tsup",dev:"tsup --watch",start:"node dist/index.js","start:stdio":"node dist/index.js","start:http":"node dist/http.js","dev:cf":"wrangler dev","deploy:cf":"wrangler deploy",test:"vitest run","test:watch":"vitest",inspector:"npx @modelcontextprotocol/inspector dist/index.js",prepack:"npm run build","pack:check":"npm pack --dry-run"},keywords:["mcp","mcp-server","crypto","bitcoin","ethereum","coingecko","gas","portfolio","claude","cursor","ai-agent"],license:"MIT",author:"Nadeem Ahmed <ebox.nadeem@gmail.com>",homepage:"https://github.com/nad33mahm3d/mcp-crypto-toolkit#readme",bugs:{url:"https://github.com/nad33mahm3d/mcp-crypto-toolkit/issues"},repository:{type:"git",url:"https://github.com/nad33mahm3d/mcp-crypto-toolkit.git"},publishConfig:{access:"public"},engines:{node:">=20"},dependencies:{"@modelcontextprotocol/sdk":"^1.0.0",zod:"^4.5.4"},devDependencies:{"@types/node":"^26.4.0",tsup:"^8.0.0",typescript:"^7.0.2",vitest:"^4.1.11",wrangler:"^4.129.0"}};var Ge=Ne.version,$e="mcp-crypto-toolkit",je="Live crypto prices, conversion, gas tracker, portfolio tools and calculators for AI agents";function Fe(){let t=new rt({name:$e,version:Ge,description:je},{capabilities:{tools:{},prompts:{}}});return Te(t),t}var ot=Fe(),at=new nt;await ot.connect(at);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-crypto-toolkit",
3
- "version": "1.2.7",
3
+ "version": "1.3.0",
4
4
  "mcpName": "io.github.nad33mahm3d/mcp-crypto-toolkit",
5
5
  "description": "Live crypto prices, conversion, gas tracker, portfolio tools and calculators for AI agents - MCP Server",
6
6
  "type": "module",
@@ -23,6 +23,8 @@
23
23
  "start": "node dist/index.js",
24
24
  "start:stdio": "node dist/index.js",
25
25
  "start:http": "node dist/http.js",
26
+ "dev:cf": "wrangler dev",
27
+ "deploy:cf": "wrangler deploy",
26
28
  "test": "vitest run",
27
29
  "test:watch": "vitest",
28
30
  "inspector": "npx @modelcontextprotocol/inspector dist/index.js",
@@ -66,6 +68,7 @@
66
68
  "@types/node": "^26.4.0",
67
69
  "tsup": "^8.0.0",
68
70
  "typescript": "^7.0.2",
69
- "vitest": "^4.1.11"
71
+ "vitest": "^4.1.11",
72
+ "wrangler": "^4.129.0"
70
73
  }
71
74
  }