mcp-crypto-toolkit 1.2.2 → 1.2.7
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 +41 -1
- package/README.md +48 -2
- package/dist/http.js +2 -1
- package/dist/index.js +1 -1
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.7] - 2026-09-04
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Align npm OIDC publish with working trusted-publisher setup: GitHub `environment: release`, `registry-url`, `npm@^11.5.1`, `--provenance`
|
|
15
|
+
|
|
16
|
+
## [1.2.6] - 2026-09-04
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- OIDC publish: remove `registry-url` from setup-node so npm uses trusted publishing instead of falling back to missing-token 404
|
|
21
|
+
|
|
22
|
+
## [1.2.5] - 2026-09-04
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- npm publish uses OIDC trusted publishing (no `NPM_TOKEN`); Node 24 in publish workflow for npm 11.5.1+
|
|
27
|
+
|
|
28
|
+
## [1.2.4] - 2026-09-04
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- HTTP transport: answer JSON-RPC notifications (e.g. `notifications/initialized`) with a non-empty 202 so reverse proxies (Cloudflare / MCP Hosting) do not hang — fixes Claude.ai Custom Connector connected with 0 tools
|
|
33
|
+
- Tool JSON Schemas: add missing `type` for `historical_price.days` and `compare_coins.coins`
|
|
34
|
+
- CORS `OPTIONS` preflight on `/mcp`
|
|
35
|
+
|
|
36
|
+
## [1.2.3] - 2026-09-03
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- `mcpName` for Official MCP Registry ownership verification
|
|
41
|
+
- `server.json` (MCP Registry metadata)
|
|
42
|
+
- `glama.json` (Glama maintainer claim)
|
|
43
|
+
- `smithery.yaml` (Smithery one-click stdio install)
|
|
44
|
+
|
|
10
45
|
## [1.2.2] - 2026-09-03
|
|
11
46
|
|
|
12
47
|
### Fixed
|
|
@@ -57,7 +92,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
57
92
|
- Binance P2P PKR path for conversions
|
|
58
93
|
- MIT license and README
|
|
59
94
|
|
|
60
|
-
[Unreleased]: https://github.com/nad33mahm3d/mcp-crypto-toolkit/compare/v1.2.
|
|
95
|
+
[Unreleased]: https://github.com/nad33mahm3d/mcp-crypto-toolkit/compare/v1.2.7...HEAD
|
|
96
|
+
[1.2.7]: https://github.com/nad33mahm3d/mcp-crypto-toolkit/compare/v1.2.6...v1.2.7
|
|
97
|
+
[1.2.6]: https://github.com/nad33mahm3d/mcp-crypto-toolkit/compare/v1.2.5...v1.2.6
|
|
98
|
+
[1.2.5]: https://github.com/nad33mahm3d/mcp-crypto-toolkit/compare/v1.2.4...v1.2.5
|
|
99
|
+
[1.2.4]: https://github.com/nad33mahm3d/mcp-crypto-toolkit/compare/v1.2.3...v1.2.4
|
|
100
|
+
[1.2.3]: https://github.com/nad33mahm3d/mcp-crypto-toolkit/compare/v1.2.2...v1.2.3
|
|
61
101
|
[1.2.2]: https://github.com/nad33mahm3d/mcp-crypto-toolkit/compare/v1.2.1...v1.2.2
|
|
62
102
|
[1.2.1]: https://github.com/nad33mahm3d/mcp-crypto-toolkit/compare/v1.2.0...v1.2.1
|
|
63
103
|
[1.2.0]: https://github.com/nad33mahm3d/mcp-crypto-toolkit/compare/v1.1.0...v1.2.0
|
package/README.md
CHANGED
|
@@ -68,6 +68,52 @@ npm run start:http
|
|
|
68
68
|
# POST http://localhost:3000/mcp
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
#### Deploy (Smithery / remote MCP) — optional
|
|
72
|
+
|
|
73
|
+
**You do not need paid hosting for discoverability.** This server already installs via `npx -y mcp-crypto-toolkit` (stdio) and is listed on the [Official MCP Registry](https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.nad33mahm3d/mcp-crypto-toolkit). Glama and PulseMCP ingest from there — no Fly/Railway bill required.
|
|
74
|
+
|
|
75
|
+
Only deploy HTTP if you want a **remote** `/mcp` endpoint (e.g. Claude.ai Custom Connector or Smithery URL publish).
|
|
76
|
+
|
|
77
|
+
#### Claude.ai Custom Connector
|
|
78
|
+
|
|
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
|
|
83
|
+
|
|
84
|
+
Do **not** put a remote URL in `claude_desktop_config.json` — that file is stdio-only. For Claude Desktop local use, prefer:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"mcpServers": {
|
|
89
|
+
"crypto-toolkit": {
|
|
90
|
+
"command": "npx",
|
|
91
|
+
"args": ["-y", "mcp-crypto-toolkit"]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Render (free — no credit card)**
|
|
98
|
+
|
|
99
|
+
1. Fork/connect repo at [render.com](https://render.com)
|
|
100
|
+
2. New → Blueprint → point at this repo (`render.yaml` uses the **Free** plan)
|
|
101
|
+
3. After deploy: `https://YOUR-SERVICE.onrender.com/health`
|
|
102
|
+
4. Smithery: publish `https://YOUR-SERVICE.onrender.com/mcp`
|
|
103
|
+
|
|
104
|
+
Free tier sleeps after ~15 min idle; first request may take ~30s to wake.
|
|
105
|
+
|
|
106
|
+
**Docker (local / your own VPS)**
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
docker build -t mcp-crypto-toolkit .
|
|
110
|
+
docker run -p 3000:3000 mcp-crypto-toolkit
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Paid PaaS (if you outgrow free tier):** Fly.io and Railway work with the included `fly.toml` / `railway.toml` but are **not free** long-term.
|
|
114
|
+
|
|
115
|
+
Optional env: `COINGECKO_API_KEY`, `COINGECKO_PRO=1`, gas oracle keys (see table below).
|
|
116
|
+
|
|
71
117
|
## Tools (9)
|
|
72
118
|
|
|
73
119
|
| Tool | Description |
|
|
@@ -137,10 +183,10 @@ See [CONTRIBUTING.md](https://github.com/nad33mahm3d/mcp-crypto-toolkit/blob/mai
|
|
|
137
183
|
|
|
138
184
|
### Releasing (maintainers)
|
|
139
185
|
|
|
140
|
-
1.
|
|
186
|
+
1. One-time: npm package **Settings → Trusted Publisher** → GitHub Actions → repo `nad33mahm3d/mcp-crypto-toolkit`, workflow `publish.yml`, environment **`release`**, allow **`npm publish`**
|
|
141
187
|
2. Update `CHANGELOG.md`
|
|
142
188
|
3. Create a GitHub Release with tag `vX.Y.Z`
|
|
143
|
-
4. Actions publishes to npm
|
|
189
|
+
4. Actions publishes to npm via OIDC (no `NPM_TOKEN`; provenance automatic)
|
|
144
190
|
|
|
145
191
|
## API Credits
|
|
146
192
|
|
package/dist/http.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{createServer as Ye}from"http";import{Server as Be}from"@modelcontextprotocol/sdk/server/index.js";import{StreamableHTTPServerTransport as Ve}from"@modelcontextprotocol/sdk/server/streamableHttp.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(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 E,H=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(t){let e=t.toLowerCase().trim();return Q[e]??e}function Ie(){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 q(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,...Ie(),...e.headers??{}}});if(r.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(!r.ok)throw new b(`CoinGecko API error: ${r.status} ${r.statusText}`,r.status,r.status>=500);return r}catch(r){if(r instanceof b)throw r;a=r instanceof Error?r:new Error(String(r)),n<o-1&&await te(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 q(`${G}/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 b(`Coin not found: ${t} (id: ${o})`);let i=r[a];if(i===void 0)throw new b(`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 j(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 q(`${G}/coins/markets?${p}`)).json();return I.set(c,l,k.PRICE),l}async function Y(t,e){let o=f(t),n=(await j(e,{ids:[o],perPage:1}))[0];if(!n)throw new b(`Coin not found: ${t} (id: ${o})`);return n}async function re(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 q(`${G}/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 ne(t,e,o){let a=f(t),n=o.toLowerCase(),r=`hist:${a}:${e}:${n}`,i=N.get(r);if(i)return i;let u=await(await q(`${G}/coins/${a}/history?date=${encodeURIComponent(e)}&localization=false`)).json();if(u.error||!u.market_data?.current_price?.[n])throw new b(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 N.set(r,p,k.HISTORICAL),p}async function oe(t,e,o){let a=f(t),n=e.toLowerCase(),r=String(o),i=`chart:${a}:${n}:${r}`,c=N.get(i);if(c)return c;let u=new URLSearchParams({vs_currency:n,days:r}),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:r,prices:l};return N.set(i,d,k.HISTORICAL),d}async function ae(t,e){return(await S(t,e)).price}function te(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 B}from"zod";async function O(){let t=D.get("usdt-pkr");if(t)return t;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 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 Ee(){let e=(await P(["tether"],["pkr"])).tether?.pkr;if(e===void 0)throw new b("Could not fetch USDT/PKR rate from CoinGecko");return Number(e.toFixed(2))}async function M(t,e){let o=e.toLowerCase();if(o==="pkr")return{converted:t,rate:1,source:"direct"};if(o==="usdt"||o==="tether"){let u=await O();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 b(`Could not get USD price for ${e}`);let i=t*r,c=await O();return{converted:Number((i*c.rate).toFixed(2)),rate:Number((r*c.rate).toFixed(2)),source:`coingecko_usd + ${c.source}`}}async function se(t,e){let o=e.toLowerCase();if(o==="pkr")return{converted:t,rate:1,source:"direct"};let a=await O(),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 b(`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 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(t){let{coin:e,vs_currency:o}=t,a=o.toLowerCase();if(a==="pkr"){let n=await O(),r=await Y(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 Y(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 ue=z.object({amount:z.number(),from:z.string(),to:z.string().describe("usd, pkr, btc, eth, etc")});async function pe(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 M(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 se(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 le}from"zod";var Te=["eth","bnb","polygon","arbitrum","base","optimism"],me=le.object({chain:le.enum(Te).default("eth")});async function de(t){let e=t.chain,o=`gas:${e}`,a=H.get(o);if(a)return a;let n,r=ee[e],i=r?process.env[r.apiKeyEnv]:void 0;if(r&&i)try{n=await Ne(e,r.url,i)}catch{}n||(n=await Ge(e));let c=await $e(n);return H.set(o,c,k.GAS),c}function V(t){return t*K/1e9}async function $e(t){let e=Z[t.chain]??"ethereum",o=null;try{o=(await S(e,"usd")).price}catch{o=null}let a=V(t.low),n=V(t.average),r=V(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 Ne(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 Ge(t){let e=X[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 qe(t)}function qe(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 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(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,F=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(F,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(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 Oe(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 _e(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 oe(t.coin,e,t.days),d=Oe(l.prices);return{mode:"range",coin:a,vs_currency:e,days:String(t.days),summary:d}}let{formatted:n}=je(t.date),r=await ne(t.coin,n,e),i=await ae(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 ye(t){let e=t.limit??10,o=await re(t.query,e);return{query:t.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(t){let e=(t.vs_currency??"usd").toLowerCase(),o=t.limit??10,a=t.order??"market_cap_desc",r=(await j(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 T}from"zod";var ve=T.object({coins:T.union([T.array(T.string()).min(2).max(5),T.string().describe("Comma-separated symbols, e.g. btc,eth,sol")]).describe("2\u20135 coins to compare"),vs_currency:T.string().default("usd")});function Fe(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 Pe(t){let e=(t.vs_currency??"usd").toLowerCase(),o=Fe(t.coins),a=o.map(f),n=await j(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 De=$.object({coin:$.string(),amount:$.number().positive()}),Ce=$.object({holdings:$.array(De).min(1).max(50).describe("List of { coin, amount } holdings"),vs_currency:$.string().default("usd")});async function Se(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 M(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 F=l.amount*h,U=h;return e==="pkr"&&c!==null&&(F*=c,U*=c),{coin:v,amount:l.amount,found:!0,unit_price:s(U,g(e,U)),value:s(F,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 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:{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:{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"]}}],He=[{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(t){return{content:[{type:"text",text:t}],isError:!0}}function C(t){return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}function xe(t){t.setRequestHandler(ze,async()=>({tools:Ke})),t.setRequestHandler(Le,async()=>({prompts:He})),t.setRequestHandler(Ue,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(Me,async e=>{let{name:o,arguments:a}=e.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 r=n instanceof Error?n.message:"Unknown error occurred";return J(r)}})}var Re=Number(process.env.PORT??3e3);function We(){let t=new Be({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:{}}});return xe(t),t}var Je=Ye(async(t,e)=>{try{if(t.method==="GET"&&(t.url==="/"||t.url==="/health")){e.writeHead(200,{"Content-Type":"application/json"}),e.end(JSON.stringify({ok:!0,name:"mcp-crypto-toolkit",mcp:"/mcp"}));return}if(t.url?.startsWith("/mcp")){let o=We(),a=new Ve({sessionIdGenerator:void 0,enableJsonResponse:!0});e.on("close",()=>{a.close(),o.close()}),await o.connect(a),await a.handleRequest(t,e);return}e.writeHead(404,{"Content-Type":"text/plain"}),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"}),e.end("Internal server error"))}});Je.listen(Re,()=>{console.error(`mcp-crypto-toolkit HTTP listening on http://localhost:${Re}/mcp`)});
|
|
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`)});
|
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,O=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=O.get("usdt-pkr");if(r)return r;try{let o={rate:await Ae(),source:"binance_p2p",lastUpdated:new Date().toISOString()};return O.set("usdt-pkr",o,k.PKR),o}catch{let o={rate:await Ee(),source:"coingecko",lastUpdated:new Date().toISOString()};return O.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,D=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(D,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 De(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=De(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 Oe=T.object({coin:T.string(),amount:T.number().positive()}),Ce=T.object({holdings:T.array(Oe).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 D=l.amount*h,U=h;return t==="pkr"&&c!==null&&(D*=c,U*=c),{coin:v,amount:l.amount,found:!0,unit_price:s(U,g(t,U)),value:s(D,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:{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:{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{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);
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-crypto-toolkit",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
|
+
"mcpName": "io.github.nad33mahm3d/mcp-crypto-toolkit",
|
|
4
5
|
"description": "Live crypto prices, conversion, gas tracker, portfolio tools and calculators for AI agents - MCP Server",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"bin": {
|
|
7
|
-
"mcp-crypto-toolkit": "
|
|
8
|
+
"mcp-crypto-toolkit": "dist/index.js"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
11
|
"dist",
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
},
|
|
50
51
|
"repository": {
|
|
51
52
|
"type": "git",
|
|
52
|
-
"url": "
|
|
53
|
+
"url": "https://github.com/nad33mahm3d/mcp-crypto-toolkit.git"
|
|
53
54
|
},
|
|
54
55
|
"publishConfig": {
|
|
55
56
|
"access": "public"
|