mcp-crypto-toolkit 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/LICENSE +21 -0
- package/README.md +154 -0
- package/dist/http.d.ts +2 -0
- package/dist/http.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +70 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [1.1.0] - 2026-09-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Tools: `search_coin`, `top_coins`, `compare_coins`, `portfolio_value`
|
|
15
|
+
- Richer `get_price` (market cap, volume, rank, ATH/ATL)
|
|
16
|
+
- `historical_price` range mode via `days`
|
|
17
|
+
- Gas USD transfer estimates + Optimism chain
|
|
18
|
+
- `profit_calc` network fee support
|
|
19
|
+
- Optional `COINGECKO_API_KEY`
|
|
20
|
+
- MCP prompts: `market-overview`, `analyze-coin`, `trade-pnl`, `gas-check`
|
|
21
|
+
- HTTP transport (`npm run start:http`)
|
|
22
|
+
- Vitest unit tests and GitHub Actions CI
|
|
23
|
+
- `demo.gif`
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Package renamed to `mcp-crypto-toolkit` (npm name conflict avoidance)
|
|
28
|
+
|
|
29
|
+
## [1.0.0] - 2026-09-02
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- Initial release: `get_price`, `convert`, `gas_tracker`, `profit_calc`, `historical_price`
|
|
34
|
+
- CoinGecko client with cache + retry
|
|
35
|
+
- Binance P2P PKR path for conversions
|
|
36
|
+
- MIT license and README
|
|
37
|
+
|
|
38
|
+
[Unreleased]: https://github.com/nad33mahm3d/mcp-crypto-toolkit/compare/v1.1.0...HEAD
|
|
39
|
+
[1.1.0]: https://github.com/nad33mahm3d/mcp-crypto-toolkit/compare/v1.0.0...v1.1.0
|
|
40
|
+
[1.0.0]: https://github.com/nad33mahm3d/mcp-crypto-toolkit/releases/tag/v1.0.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mcp-crypto-price contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# mcp-crypto-toolkit
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/mcp-crypto-toolkit)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://modelcontextprotocol.io)
|
|
6
|
+
[](https://github.com/nad33mahm3d/mcp-crypto-toolkit/actions/workflows/ci.yml)
|
|
7
|
+
|
|
8
|
+
**Live crypto prices, conversion, gas tracker, portfolio tools, and calculators for AI agents.**
|
|
9
|
+
|
|
10
|
+
Ask Claude: *What's ETH doing? Top 10 coins? Gas on Base? What's my portfolio worth?*
|
|
11
|
+
|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- Live prices with market cap, volume, rank, ATH/ATL (CoinGecko)
|
|
17
|
+
- Convert between crypto and fiat (optimized PKR via Binance P2P when needed)
|
|
18
|
+
- Search coins by name/symbol
|
|
19
|
+
- Top coins + gainers/losers snapshot
|
|
20
|
+
- Compare 2–5 coins side by side
|
|
21
|
+
- Portfolio valuation (batch holdings)
|
|
22
|
+
- Historical point-in-time + range charts (7d/30d/…)
|
|
23
|
+
- EVM gas tracker with USD transfer estimates
|
|
24
|
+
- Profit/loss calculator with exchange + network fees
|
|
25
|
+
- MCP prompts for common workflows
|
|
26
|
+
- **Zero config** — no API key required (optional `COINGECKO_API_KEY` for higher limits)
|
|
27
|
+
- stdio + HTTP transports
|
|
28
|
+
|
|
29
|
+
## Quick Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx -y mcp-crypto-toolkit
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
### Claude Desktop / Cursor / Windsurf
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"mcpServers": {
|
|
42
|
+
"crypto-toolkit": {
|
|
43
|
+
"command": "npx",
|
|
44
|
+
"args": ["-y", "mcp-crypto-toolkit"]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Local build:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"mcpServers": {
|
|
55
|
+
"crypto-toolkit": {
|
|
56
|
+
"command": "node",
|
|
57
|
+
"args": ["/ABSOLUTE/PATH/TO/mcp-crypto-toolkit/dist/index.js"]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### HTTP server
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm run build
|
|
67
|
+
npm run start:http
|
|
68
|
+
# POST http://localhost:3000/mcp
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Tools (9)
|
|
72
|
+
|
|
73
|
+
| Tool | Description |
|
|
74
|
+
|------|-------------|
|
|
75
|
+
| `get_price` | Live price + market cap, volume, rank, ATH/ATL |
|
|
76
|
+
| `convert` | Crypto ↔ fiat/crypto conversion |
|
|
77
|
+
| `search_coin` | Search by name or symbol |
|
|
78
|
+
| `top_coins` | Top N by market cap/volume + gainers/losers |
|
|
79
|
+
| `compare_coins` | Side-by-side compare 2–5 coins |
|
|
80
|
+
| `portfolio_value` | Value a list of holdings |
|
|
81
|
+
| `historical_price` | Point date or range chart + investment snapshot |
|
|
82
|
+
| `gas_tracker` | Gas fees + estimated transfer cost (USD) |
|
|
83
|
+
| `profit_calc` | P&L, ROI, break-even (fees + network fee) |
|
|
84
|
+
|
|
85
|
+
## Prompts
|
|
86
|
+
|
|
87
|
+
| Prompt | Purpose |
|
|
88
|
+
|--------|---------|
|
|
89
|
+
| `market-overview` | Top coins + market tone |
|
|
90
|
+
| `analyze-coin` | Price + 7d trend for one coin |
|
|
91
|
+
| `trade-pnl` | Walk through a P&L calc |
|
|
92
|
+
| `gas-check` | Gas + transfer cost advice |
|
|
93
|
+
|
|
94
|
+
## Example prompts
|
|
95
|
+
|
|
96
|
+
1. *What's the current price of Bitcoin and its market cap?*
|
|
97
|
+
2. *Search for coins matching "pepe"*
|
|
98
|
+
3. *Show top 10 coins and today's gainers*
|
|
99
|
+
4. *Compare BTC, ETH, and SOL*
|
|
100
|
+
5. *Value my portfolio: 0.5 BTC and 2 ETH in USD*
|
|
101
|
+
6. *What was ETH on 01-01-2024, and what would $1000 then be worth now?*
|
|
102
|
+
7. *Show SOL's 30-day price range*
|
|
103
|
+
8. *What's gas on Base right now in USD?*
|
|
104
|
+
9. *I bought at 100, sold at 120, qty 10, 0.1% fees — profit?*
|
|
105
|
+
|
|
106
|
+
## Development
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
git clone https://github.com/nad33mahm3d/mcp-crypto-toolkit
|
|
110
|
+
cd mcp-crypto-toolkit
|
|
111
|
+
npm install
|
|
112
|
+
npm test
|
|
113
|
+
npm run build
|
|
114
|
+
npm run inspector
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Optional env
|
|
118
|
+
|
|
119
|
+
| Variable | Purpose |
|
|
120
|
+
|----------|---------|
|
|
121
|
+
| `COINGECKO_API_KEY` | Higher CoinGecko rate limits (still optional) |
|
|
122
|
+
| `COINGECKO_PRO=1` | Use Pro header with your key |
|
|
123
|
+
| `ETHERSCAN_API_KEY` / `BSCSCAN_API_KEY` / … | Better gas oracles |
|
|
124
|
+
| `OPTIMISM_API_KEY` | Optimism gas oracle |
|
|
125
|
+
| `PORT` | HTTP server port (default 3000) |
|
|
126
|
+
|
|
127
|
+
Without gas API keys, gas falls back to Blocknative then static estimates.
|
|
128
|
+
|
|
129
|
+
## Contributing
|
|
130
|
+
|
|
131
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup, PR guidelines, and the release process.
|
|
132
|
+
|
|
133
|
+
- [Code of Conduct](./CODE_OF_CONDUCT.md)
|
|
134
|
+
- [Security Policy](./SECURITY.md)
|
|
135
|
+
- [Support](./SUPPORT.md)
|
|
136
|
+
- [Changelog](./CHANGELOG.md)
|
|
137
|
+
|
|
138
|
+
### Releasing (maintainers)
|
|
139
|
+
|
|
140
|
+
1. Add `NPM_TOKEN` repo secret (npm Automation token)
|
|
141
|
+
2. Update `CHANGELOG.md`
|
|
142
|
+
3. Create a GitHub Release with tag `vX.Y.Z`
|
|
143
|
+
4. Actions publishes to npm with provenance automatically
|
|
144
|
+
|
|
145
|
+
## API Credits
|
|
146
|
+
|
|
147
|
+
- [CoinGecko](https://www.coingecko.com/) — prices & markets (free tier, no key required)
|
|
148
|
+
- [Binance P2P](https://p2p.binance.com/) — USDT/PKR when converting to PKR
|
|
149
|
+
- [Etherscan](https://etherscan.io/) family — optional gas oracles
|
|
150
|
+
- [Blocknative](https://www.blocknative.com/) — gas fallback
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT — see [LICENSE](./LICENSE)
|
package/dist/http.d.ts
ADDED
package/dist/http.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
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`)});
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
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);
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-crypto-toolkit",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Live crypto prices, conversion, gas tracker, portfolio tools and calculators for AI agents - MCP Server",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mcp-crypto-toolkit": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE",
|
|
13
|
+
"CHANGELOG.md"
|
|
14
|
+
],
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"start": "node dist/index.js",
|
|
23
|
+
"start:stdio": "node dist/index.js",
|
|
24
|
+
"start:http": "node dist/http.js",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"test:watch": "vitest",
|
|
27
|
+
"inspector": "npx @modelcontextprotocol/inspector dist/index.js",
|
|
28
|
+
"prepack": "npm run build",
|
|
29
|
+
"pack:check": "npm pack --dry-run"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mcp",
|
|
33
|
+
"mcp-server",
|
|
34
|
+
"crypto",
|
|
35
|
+
"bitcoin",
|
|
36
|
+
"ethereum",
|
|
37
|
+
"coingecko",
|
|
38
|
+
"gas",
|
|
39
|
+
"portfolio",
|
|
40
|
+
"claude",
|
|
41
|
+
"cursor",
|
|
42
|
+
"ai-agent"
|
|
43
|
+
],
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"author": "Nadeem Ahmed <ebox.nadeem@gmail.com>",
|
|
46
|
+
"homepage": "https://github.com/nad33mahm3d/mcp-crypto-toolkit#readme",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/nad33mahm3d/mcp-crypto-toolkit/issues"
|
|
49
|
+
},
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/nad33mahm3d/mcp-crypto-toolkit.git"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=18"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
62
|
+
"zod": "^3.23.0"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@types/node": "^20.0.0",
|
|
66
|
+
"tsup": "^8.0.0",
|
|
67
|
+
"typescript": "^5.4.0",
|
|
68
|
+
"vitest": "^3.0.0"
|
|
69
|
+
}
|
|
70
|
+
}
|