@mindjack/mcp
Advanced tools
| /** | ||
| * What each tool returns, as JSON Schema. | ||
| * | ||
| * A tool that declares an outputSchema must return structuredContent matching | ||
| * it, so these are a promise and not documentation: a client is entitled to | ||
| * validate against them and to plan a chain of calls from the field names | ||
| * without spending a call to discover the shape. | ||
| * | ||
| * NOT written by hand. Every priced route states its own outputSchema in the | ||
| * 402 challenge, which costs nothing to read, and the free routes answer 200, | ||
| * so these were harvested from the API that owns them and then reduced to the | ||
| * top level. Hand-copied schemas would have become a second contract that | ||
| * drifts from the first one quietly; regenerate rather than edit. | ||
| * | ||
| * Reduced deliberately. The full server schemas are 22KB and the tool list is | ||
| * already 16KB, all of it loaded into the model's context on every request. | ||
| * The top-level names and types are what a caller plans against; the nested | ||
| * structure is discoverable from the response it just paid for. | ||
| */ | ||
| export const OUTPUT_SCHEMAS = { | ||
| check_token: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "facts": { | ||
| "type": "object", | ||
| "description": "Counts AND the share of supply behind each: snipers, insiders, fresh wallets, wallet groups,..." | ||
| }, | ||
| "mint": { | ||
| "type": "string", | ||
| "description": "Solana token mint address (base58)." | ||
| }, | ||
| "name": { | ||
| "type": "string" | ||
| }, | ||
| "rug_risk": { | ||
| "type": "object" | ||
| }, | ||
| "safety": { | ||
| "type": "object", | ||
| "description": "Mint/freeze authority, LP and timelock state. Reports unknown as unknown, never as zero." | ||
| }, | ||
| "symbol": { | ||
| "type": "string" | ||
| } | ||
| } | ||
| }, | ||
| inspect_token: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "activity": { | ||
| "type": "object", | ||
| "description": "transactions analysed and fee cost." | ||
| }, | ||
| "groups": { | ||
| "type": "object" | ||
| }, | ||
| "holder_wealth": { | ||
| "type": "object", | ||
| "description": "top-50 SOL distribution; a median near zero is a launch distributed to nobody." | ||
| }, | ||
| "mint": { | ||
| "type": "string", | ||
| "description": "Solana token mint address (base58)." | ||
| }, | ||
| "top_holders": { | ||
| "type": "array", | ||
| "description": "rank, wallet, supply_pct, is_whale, is_notable." | ||
| }, | ||
| "tracked_traders": { | ||
| "type": "object", | ||
| "description": "Activity from traders we follow by name." | ||
| }, | ||
| "trading_patterns": { | ||
| "type": "object", | ||
| "description": "scalp score and wash-trading share." | ||
| }, | ||
| "wallet_classes": { | ||
| "type": "object", | ||
| "description": "snipers, insiders, fresh_wallets, early_buyers." | ||
| } | ||
| } | ||
| }, | ||
| token_report: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "depth": { | ||
| "type": "string", | ||
| "description": "Which tiers are included.", | ||
| "enum": [ | ||
| "core", | ||
| "full" | ||
| ] | ||
| }, | ||
| "identity": { | ||
| "type": "object", | ||
| "description": "The full body of the endpoint it names, exactly as that endpoint returns it." | ||
| }, | ||
| "inspect": { | ||
| "type": "object", | ||
| "description": "The full body of the endpoint it names, exactly as that endpoint returns it." | ||
| }, | ||
| "mint": { | ||
| "type": "string", | ||
| "description": "The token this describes." | ||
| }, | ||
| "says": { | ||
| "type": "string", | ||
| "description": "The finding in one plain sentence, safe to quote." | ||
| }, | ||
| "screen": { | ||
| "type": "object", | ||
| "description": "The full body of the endpoint it names, exactly as that endpoint returns it." | ||
| } | ||
| } | ||
| }, | ||
| token_identity: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "creator": { | ||
| "type": "object", | ||
| "description": "address, allocation_pct, has_sold." | ||
| }, | ||
| "identity": { | ||
| "type": "object", | ||
| "description": "holders_checked, holders_with_history (a COUNT), coverage_pct, prior_appearances,..." | ||
| }, | ||
| "mint": { | ||
| "type": "string", | ||
| "description": "Solana token mint address (base58)." | ||
| }, | ||
| "sibling_outcomes": { | ||
| "type": "object", | ||
| "description": "known vs rugged among the siblings." | ||
| }, | ||
| "sibling_tokens": { | ||
| "type": "array", | ||
| "description": "Earlier tokens these wallets ran: mint, seen_at, shared_wallets, rugged." | ||
| }, | ||
| "upside": { | ||
| "type": "object", | ||
| "description": "Measured 2x/5x rate for tokens whose holders carried this much winning history, against..." | ||
| } | ||
| } | ||
| }, | ||
| find_tokens: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "count": { | ||
| "type": "integer" | ||
| }, | ||
| "filters": { | ||
| "type": "object", | ||
| "description": "The filters actually applied, echoed back — a caller should not have to guess whether a..." | ||
| }, | ||
| "says": { | ||
| "type": "string", | ||
| "description": "The finding in one plain sentence, safe to quote." | ||
| }, | ||
| "tokens": { | ||
| "type": "array" | ||
| } | ||
| } | ||
| }, | ||
| check_wallet: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "history": { | ||
| "type": "object", | ||
| "description": "Appearances, roles it recurs in, and its realised record across the index." | ||
| }, | ||
| "known": { | ||
| "type": "boolean", | ||
| "description": "False when we have never seen this wallet in an indexed token. Never billed." | ||
| }, | ||
| "says": { | ||
| "type": "string", | ||
| "description": "The finding in one plain sentence, safe to quote." | ||
| }, | ||
| "trading": { | ||
| "type": "object" | ||
| }, | ||
| "wallet": { | ||
| "type": "string", | ||
| "description": "Solana wallet address (base58)." | ||
| } | ||
| } | ||
| }, | ||
| can_i_exit: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "checked_at_sol_usd": { | ||
| "type": "number", | ||
| "description": "SOL price used to size the ladder, so a dollar rung can be reproduced later." | ||
| }, | ||
| "exit": { | ||
| "type": "object", | ||
| "description": "verdict (clear/elevated/thin/trapped/blocked), action, worst_retained_pct, and a ladder of..." | ||
| }, | ||
| "method": { | ||
| "type": "string", | ||
| "description": "How the answer was obtained, and what it cannot see. Quotes only — nothing is signed." | ||
| }, | ||
| "mint": { | ||
| "type": "string", | ||
| "description": "Solana token mint address (base58)." | ||
| }, | ||
| "says": { | ||
| "type": "string", | ||
| "description": "The finding in one plain sentence, safe to quote." | ||
| } | ||
| } | ||
| }, | ||
| token_changes: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "changes": { | ||
| "type": "object", | ||
| "description": "Movement against the baseline: who left, who grew, and the supply behind it." | ||
| }, | ||
| "mint": { | ||
| "type": "string", | ||
| "description": "Solana token mint address (base58)." | ||
| }, | ||
| "ready": { | ||
| "type": "boolean", | ||
| "description": "False when we hold no holder baseline for this token; nothing is charged in that case." | ||
| }, | ||
| "says": { | ||
| "type": "string", | ||
| "description": "The finding in one plain sentence, safe to quote." | ||
| } | ||
| } | ||
| }, | ||
| token_price_path: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "coverage_note": { | ||
| "type": "string", | ||
| "description": "Where our feed starts, so the path is not mistaken for the token's whole life." | ||
| }, | ||
| "mint": { | ||
| "type": "string", | ||
| "description": "Solana token mint address (base58)." | ||
| }, | ||
| "path": { | ||
| "type": "object", | ||
| "description": "analysed_at, first/last tick, tick count, market cap at analysis / latest / peak / trough,..." | ||
| }, | ||
| "says": { | ||
| "type": "string", | ||
| "description": "The finding in one plain sentence, safe to quote." | ||
| } | ||
| } | ||
| }, | ||
| find_serial_insiders: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "count": { | ||
| "type": "integer" | ||
| }, | ||
| "filters": { | ||
| "type": "object" | ||
| }, | ||
| "says": { | ||
| "type": "string", | ||
| "description": "The finding in one plain sentence, safe to quote." | ||
| }, | ||
| "wallets": { | ||
| "type": "array", | ||
| "description": "wallet, insider_in, also_held, early_investor_in, avg_supply_pct, realized_sol,..." | ||
| } | ||
| } | ||
| }, | ||
| compare_tokens: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "compared": { | ||
| "type": "array" | ||
| }, | ||
| "count": { | ||
| "type": "integer" | ||
| }, | ||
| "in_all": { | ||
| "type": "integer" | ||
| }, | ||
| "not_covered": { | ||
| "type": "array", | ||
| "description": "mints we do not hold; the rest is still answered, as coverage 'partial'." | ||
| }, | ||
| "says": { | ||
| "type": "string", | ||
| "description": "The finding in one plain sentence, safe to quote." | ||
| }, | ||
| "shared": { | ||
| "type": "array", | ||
| "description": "wallet, in_tokens, mints, roles (holder / insider / early / sniper)." | ||
| } | ||
| } | ||
| }, | ||
| test_hypothesis: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "cohort": { | ||
| "type": "object", | ||
| "description": "n, rug_pct, base_rate_pct, rug_lift_vs_base, median_peak_gain_pct, p75/p90_peak_gain_pct,..." | ||
| }, | ||
| "disclaimer": { | ||
| "type": "string", | ||
| "description": "Measured history, not a forecast, and not adjusted for slippage or fees." | ||
| }, | ||
| "filters": { | ||
| "type": "object", | ||
| "description": "The filters applied, echoed back." | ||
| }, | ||
| "says": { | ||
| "type": "string", | ||
| "description": "The finding in one plain sentence, safe to quote." | ||
| }, | ||
| "window_days": { | ||
| "type": "integer" | ||
| } | ||
| } | ||
| }, | ||
| get_scorecard: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "bands": { | ||
| "type": "array" | ||
| }, | ||
| "base_rate_pct": { | ||
| "type": "number" | ||
| }, | ||
| "calibrated_at": { | ||
| "type": "string" | ||
| }, | ||
| "says": { | ||
| "type": "string" | ||
| }, | ||
| "window_days": { | ||
| "type": "number" | ||
| } | ||
| } | ||
| }, | ||
| get_sample: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "identity": { | ||
| "type": "object" | ||
| }, | ||
| "inspect": { | ||
| "type": "object" | ||
| }, | ||
| "mint": { | ||
| "type": "string" | ||
| }, | ||
| "next": { | ||
| "type": "array" | ||
| }, | ||
| "not_shown": { | ||
| "type": "object" | ||
| }, | ||
| "says": { | ||
| "type": "string" | ||
| }, | ||
| "screen": { | ||
| "type": "object" | ||
| } | ||
| } | ||
| }, | ||
| token_graph: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "edges": { | ||
| "type": "array", | ||
| "description": "wallet_a/wallet_b ties with strength and confidence." | ||
| }, | ||
| "edges_total": { | ||
| "type": "integer", | ||
| "description": "True edge count; compare against the returned array to detect truncation." | ||
| }, | ||
| "members": { | ||
| "type": "array", | ||
| "description": "Which wallet sits in which cluster, and its role." | ||
| }, | ||
| "members_total": { | ||
| "type": "integer" | ||
| }, | ||
| "mint": { | ||
| "type": "string", | ||
| "description": "Solana token mint address (base58)." | ||
| }, | ||
| "says": { | ||
| "type": "string", | ||
| "description": "The finding in one plain sentence, safe to quote." | ||
| }, | ||
| "wash_wallets": { | ||
| "type": "array" | ||
| } | ||
| } | ||
| }, | ||
| token_wallets: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "early_investors": { | ||
| "type": "array", | ||
| "description": "Addresses with funding source, cluster membership and flags. Capped at 100; the matching..." | ||
| }, | ||
| "early_total": { | ||
| "type": "integer" | ||
| }, | ||
| "fresh_total": { | ||
| "type": "integer" | ||
| }, | ||
| "fresh_wallets": { | ||
| "type": "array", | ||
| "description": "Addresses with funding source, cluster membership and flags. Capped at 100; the matching..." | ||
| }, | ||
| "insiders": { | ||
| "type": "array", | ||
| "description": "Addresses with funding source, cluster membership and flags. Capped at 100; the matching..." | ||
| }, | ||
| "insiders_total": { | ||
| "type": "integer" | ||
| }, | ||
| "kols": { | ||
| "type": "array", | ||
| "description": "Addresses with funding source, cluster membership and flags. Capped at 100; the matching..." | ||
| }, | ||
| "kols_total": { | ||
| "type": "integer" | ||
| }, | ||
| "mint": { | ||
| "type": "string" | ||
| }, | ||
| "says": { | ||
| "type": "string", | ||
| "description": "The finding in one plain sentence, safe to quote." | ||
| }, | ||
| "scalpers": { | ||
| "type": "array", | ||
| "description": "Addresses with funding source, cluster membership and flags. Capped at 100; the matching..." | ||
| }, | ||
| "snipers": { | ||
| "type": "array", | ||
| "description": "Addresses with funding source, cluster membership and flags. Capped at 100; the matching..." | ||
| }, | ||
| "snipers_total": { | ||
| "type": "integer" | ||
| }, | ||
| "wash_total": { | ||
| "type": "integer" | ||
| }, | ||
| "wash_wallets": { | ||
| "type": "array", | ||
| "description": "Addresses with funding source, cluster membership and flags. Capped at 100; the matching..." | ||
| } | ||
| } | ||
| }, | ||
| token_web: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "common_wallets": { | ||
| "type": "array", | ||
| "description": "The shared wallets themselves, with which launches each ties." | ||
| }, | ||
| "mint": { | ||
| "type": "string" | ||
| }, | ||
| "says": { | ||
| "type": "string", | ||
| "description": "The finding in one plain sentence, safe to quote." | ||
| }, | ||
| "tokens": { | ||
| "type": "array", | ||
| "description": "Launches sharing wallets with this one: mint, shared wallet count, outcome and the highest..." | ||
| }, | ||
| "web_wallet_pool": { | ||
| "type": "integer", | ||
| "description": "Wallets considered when building the web." | ||
| } | ||
| } | ||
| }, | ||
| wallet_network: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "direct": { | ||
| "type": "array", | ||
| "description": "Counterparts, each with the wallet, interaction count, shared tokens and transfer direction..." | ||
| }, | ||
| "direct_total": { | ||
| "type": "integer", | ||
| "description": "Counterparts found, before the cap." | ||
| }, | ||
| "edge_total": { | ||
| "type": "integer", | ||
| "description": "Edges behind both rings." | ||
| }, | ||
| "says": { | ||
| "type": "string", | ||
| "description": "The finding in one plain sentence, safe to quote." | ||
| }, | ||
| "second_hop": { | ||
| "type": "array", | ||
| "description": "A bounded second ring, reached through the direct counterparts." | ||
| }, | ||
| "wallet": { | ||
| "type": "string", | ||
| "description": "The wallet asked about." | ||
| } | ||
| } | ||
| }, | ||
| kol_leaderboard: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "count": { | ||
| "type": "integer" | ||
| }, | ||
| "filters": { | ||
| "type": "object" | ||
| }, | ||
| "kols": { | ||
| "type": "array", | ||
| "description": "wallet, name, handle, verified, followers, lifetime {trades, volume_sol, realized_sol,..." | ||
| }, | ||
| "says": { | ||
| "type": "string", | ||
| "description": "The finding in one plain sentence, safe to quote." | ||
| } | ||
| } | ||
| }, | ||
| kol_record: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "kol": { | ||
| "type": "object", | ||
| "description": "Identity and lifetime stats. Null when the address is not a tracked KOL; nothing is charged..." | ||
| }, | ||
| "recent_trades": { | ||
| "type": "array", | ||
| "description": "Latest trades: side, sol, realized_sol, roi_pct, entry_mcap, timestamp." | ||
| }, | ||
| "tokens": { | ||
| "type": "array", | ||
| "description": "Per-token rollup: mint, symbol, buys, sells, volume_sol, realized_sol, first and last trade..." | ||
| } | ||
| } | ||
| }, | ||
| funder_networks: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "count": { | ||
| "type": "integer" | ||
| }, | ||
| "filters": { | ||
| "type": "object" | ||
| }, | ||
| "funders": { | ||
| "type": "array", | ||
| "description": "funder, funder_known (exchange name when we know the address, else null), wallets_funded,..." | ||
| }, | ||
| "says": { | ||
| "type": "string", | ||
| "description": "The finding in one plain sentence, safe to quote." | ||
| } | ||
| } | ||
| }, | ||
| search_tokens: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "count": { | ||
| "type": "integer" | ||
| }, | ||
| "query": { | ||
| "type": "object" | ||
| }, | ||
| "says": { | ||
| "type": "string", | ||
| "description": "The finding in one plain sentence, safe to quote." | ||
| }, | ||
| "tokens": { | ||
| "type": "array", | ||
| "description": "mint, symbol, name, analyzed_at, mcap_at_analysis, platform, holders, insiders, snipers,..." | ||
| } | ||
| } | ||
| }, | ||
| search: { | ||
| "type": "object", | ||
| "properties": { | ||
| "results": { | ||
| "type": "array", | ||
| "description": "Rows of {id, title, url}. id is the mint; pass it to fetch." | ||
| }, | ||
| "count": { | ||
| "type": "number" | ||
| }, | ||
| "says": { | ||
| "type": "string" | ||
| }, | ||
| "_meta": { | ||
| "type": "object", | ||
| "description": "coverage, billing and price_usd for this call." | ||
| } | ||
| } | ||
| }, | ||
| fetch: { | ||
| "type": "object", | ||
| "properties": { | ||
| "id": { | ||
| "type": "string", | ||
| "description": "The mint this document is about." | ||
| }, | ||
| "title": { | ||
| "type": "string" | ||
| }, | ||
| "url": { | ||
| "type": "string", | ||
| "description": "Public page for the token, or null if the id was not an address." | ||
| }, | ||
| "text": { | ||
| "type": "string", | ||
| "description": "The full report as JSON text." | ||
| }, | ||
| "metadata": { | ||
| "type": "object" | ||
| } | ||
| } | ||
| }, | ||
| get_coverage: { | ||
| "type": "object", | ||
| "properties": { | ||
| "_meta": { | ||
| "type": "object" | ||
| }, | ||
| "history_from": { | ||
| "type": "string" | ||
| }, | ||
| "index": { | ||
| "type": "object" | ||
| }, | ||
| "not_covered": { | ||
| "type": "object" | ||
| }, | ||
| "platforms": { | ||
| "type": "array" | ||
| }, | ||
| "pricing": { | ||
| "type": "object" | ||
| }, | ||
| "start_here": { | ||
| "type": "object" | ||
| }, | ||
| "stats": { | ||
| "type": "object" | ||
| }, | ||
| "universe": { | ||
| "type": "string" | ||
| } | ||
| } | ||
| }, | ||
| get_balance: { | ||
| "type": "object", | ||
| "properties": { | ||
| "key_prefix": { | ||
| "type": "string" | ||
| }, | ||
| "credit_balance": { | ||
| "type": "number", | ||
| "description": "Credits left. A new key starts at 0." | ||
| }, | ||
| "free_remaining": { | ||
| "type": "number" | ||
| }, | ||
| "plan": { | ||
| "type": "string" | ||
| }, | ||
| "plan_credits_per_month": { | ||
| "type": "number" | ||
| }, | ||
| "plan_expires_at": { | ||
| "type": "number" | ||
| } | ||
| } | ||
| }, | ||
| }; |
+2
-2
| { | ||
| "name": "@mindjack/mcp", | ||
| "version": "1.3.0", | ||
| "version": "1.4.0", | ||
| "mcpName": "xyz.mindjack/mcp", | ||
@@ -18,3 +18,3 @@ "description": "On-chain risk and identity intelligence for Solana tokens, as MCP tools. Calibrated rug verdicts with a published hit rate, named wallets and their networks, the KOL trade ledger, and what happened to every launch we have indexed since March 2026.", | ||
| "dependencies": { | ||
| "@modelcontextprotocol/sdk": "^1.0.0" | ||
| "@modelcontextprotocol/sdk": "^1.30.0" | ||
| }, | ||
@@ -21,0 +21,0 @@ "keywords": [ |
+54
-0
@@ -44,2 +44,3 @@ # @mindjack/mcp | ||
| | `search_tokens` | $0.005 /page | Find any token we ever analysed, by symbol, name or mint | | ||
| | `search` | $0.005 /page | The same search, shaped for research clients: `{id, title, url}` rows to cite or pass to `fetch` | | ||
| | `inspect_token` | $0.005 | Who is holding it, and what kind of wallets | | ||
@@ -54,2 +55,3 @@ | `token_wallets` | $0.005 | The named wallets behind it: insiders, snipers, fresh, wash, KOLs — with funders and clusters | | ||
| | `token_report` | $0.025 | Everything on one token in one call; `depth: "full"` is $0.07 | | ||
| | `fetch` | $0.025 | The same report as one document, keyed by the `id` a `search` result gave you | | ||
| | `token_changes` | $0.025 | Who sold since we analysed it (reads the chain now) | | ||
@@ -64,5 +66,57 @@ | `test_hypothesis` | $0.025 | What happened to tokens shaped like this | | ||
| `search` and `fetch` are aliases, not extra products: same endpoints, same | ||
| prices, same billing as `search_tokens` and `token_report`. They exist under | ||
| those exact names because ChatGPT's deep research and company knowledge modes | ||
| look for them by name and connect to nothing without them. | ||
| Live feeds (new launches, watched wallets, KOL trades) are server-sent event | ||
| streams and do not map to MCP tools — the endpoint guide below covers them. | ||
| ## Resources | ||
| Four read-only documents, all free, attached once rather than called per task. | ||
| A client can cache them; the model does not have to decide to fetch them. | ||
| | URI | What it is | | ||
| | --- | --- | | ||
| | `mindjack://coverage` | The window we hold, what is in it, and a live mint guaranteed to have data | | ||
| | `mindjack://scorecard` | Every calibrated band with the collapse rate measured for it and its sample size | | ||
| | `mindjack://prices` | Every priced route with its price, asset, network and receiving address | | ||
| | `mindjack://sample` | One real token answered in full, free, so you can read the shape before buying it | | ||
| ## Prompts | ||
| Three workflows, written down. Each is the order of calls that answers a real | ||
| question, with what to read out of each step and when the next one is worth its | ||
| price. | ||
| | Prompt | Arguments | Answers | | ||
| | --- | --- | --- | | ||
| | `vet_before_buying` | `mint` | Should I take a position in this token? | | ||
| | `find_candidates` | `hours`, `min_mcap` (optional) | What launched recently that is worth a closer look? | | ||
| | `vet_counterparty` | `address` | Who is this wallet and what has it done before? | | ||
| MCP passes prompt arguments as strings, so send `"6"` rather than `6`. | ||
| ## Using it | ||
| Every tool declares an `outputSchema` and returns `structuredContent` beside | ||
| the text, so a client can validate the answer and plan the next call from the | ||
| field names without paying to discover the shape. | ||
| A first run, in the order the server itself recommends: | ||
| ``` | ||
| get_coverage -> free; the window, and a mint that works | ||
| find_tokens hours=24 max_rug_pct=45 -> candidates, each with a measured verdict | ||
| check_token mint=<from above> -> $0.001; structure and collapse speed | ||
| token_identity mint=<survivor> -> $0.025; who is holding it and how their | ||
| other tokens ended | ||
| ``` | ||
| `max_rug_pct` is worth one warning: it is a **measured** collapse frequency for | ||
| a calibrated band, not a score that starts at zero. The safest band we publish | ||
| still rugged about 35% of the time, so a threshold under that matches nothing | ||
| at any window length. The universe base rate is 45%. | ||
| ## What makes this different | ||
@@ -69,0 +123,0 @@ |
+382
-2
@@ -26,4 +26,9 @@ #!/usr/bin/env node | ||
| CallToolRequestSchema, | ||
| GetPromptRequestSchema, | ||
| ListPromptsRequestSchema, | ||
| ListResourcesRequestSchema, | ||
| ListToolsRequestSchema, | ||
| ReadResourceRequestSchema, | ||
| } from "@modelcontextprotocol/sdk/types.js"; | ||
| import { OUTPUT_SCHEMAS } from "./output-schemas.js"; | ||
| import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; | ||
@@ -620,3 +625,85 @@ import { homedir } from "node:os"; | ||
| }, | ||
| // ChatGPT's deep research and company knowledge modes look for two tools by | ||
| // name, `search` and `fetch`, and connect to nothing without them. Asked to | ||
| // learn Mindjack, ChatGPT reported there was nothing here for it: twenty-four | ||
| // tools, and not the two it looks for. | ||
| // | ||
| // Aliases, not new products. Same endpoints, same prices and same billing as | ||
| // search_tokens and token_report; giving them their own price would have | ||
| // added a number that could disagree with the published list. | ||
| { | ||
| name: "search", | ||
| description: | ||
| "[$0.005/page] Find Solana tokens in the analysed catalogue by symbol, " + | ||
| "name, or exact mint. Returns {id, title, url} rows; pass an id to " + | ||
| "`fetch` for the full document. Same endpoint and same price as " + | ||
| "search_tokens, in the shape research clients expect.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: { | ||
| query: { | ||
| type: "string", | ||
| description: "Symbol, name fragment, or an exact mint address.", | ||
| }, | ||
| limit: P_LIMIT, | ||
| }, | ||
| required: ["query"], | ||
| }, | ||
| run: async (a) => { | ||
| const params = new URLSearchParams(); | ||
| if (a.query) params.set("q", a.query); | ||
| if (a.limit !== undefined && a.limit !== null) params.set("limit", a.limit); | ||
| const out = await call(`/v1/search?${params.toString()}`); | ||
| if (!out || out.error) return out; | ||
| const results = (out.tokens || []) | ||
| .map((t) => ({ t, url: tokenUrl(t.mint) })) | ||
| // A row we cannot link is a row we cannot cite, so it is dropped | ||
| // rather than emitted with a blank or a broken address. | ||
| .filter(({ url }) => url) | ||
| .map(({ t, url }) => ({ | ||
| id: t.mint, | ||
| title: [t.symbol, t.name].filter(Boolean).join(" — ") || t.mint, | ||
| url, | ||
| })); | ||
| return { results, count: results.length, says: out.says, _meta: out._meta }; | ||
| }, | ||
| }, | ||
| { | ||
| name: "fetch", | ||
| description: | ||
| "[$0.025] Everything Mindjack holds on one token as a single document: " + | ||
| "the calibrated rug verdict with its measured hit rate, who is holding " + | ||
| "and how they are connected, and whether it can still be sold. Takes an " + | ||
| "id from `search` — a Solana mint address. Same endpoint and price " + | ||
| "as token_report.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: { | ||
| id: { | ||
| type: "string", | ||
| description: "A Solana mint address, usually from `search`.", | ||
| }, | ||
| }, | ||
| required: ["id"], | ||
| }, | ||
| run: async (a) => { | ||
| const out = await call(`/v1/report/${encodeURIComponent(a.id || "")}`); | ||
| if (!out || out.error) return out; | ||
| const screen = out.screen || {}; | ||
| const identity = out.identity || {}; | ||
| const mint = screen.mint || identity.mint || out.mint || ""; | ||
| const title = | ||
| [screen.symbol || identity.symbol, screen.name || identity.name] | ||
| .filter(Boolean) | ||
| .join(" — ") || mint || "token"; | ||
| return { | ||
| id: mint, | ||
| title, | ||
| url: tokenUrl(mint), | ||
| text: JSON.stringify(out, null, 2), | ||
| metadata: out._meta, | ||
| }; | ||
| }, | ||
| }, | ||
| { | ||
| name: "get_coverage", | ||
@@ -655,5 +742,38 @@ description: | ||
| // Handed to the model once, at the handshake, instead of being repeated in | ||
| // twenty-six descriptions. It says the three things that are not derivable | ||
| // from a tool list: what the index actually contains, that the numbers are | ||
| // measured frequencies rather than opinions, and which order the tools go in. | ||
| // Kept short on purpose — it is in the context of every request that follows. | ||
| const INSTRUCTIONS = [ | ||
| "Mindjack answers from a point-in-time record of Solana launches: holder", | ||
| "distribution, insider, fresh-wallet and sniper detection as they stood at", | ||
| "the moment each token migrated. That state cannot be rebuilt from the chain", | ||
| "afterwards, which is why the window is the product.", | ||
| "", | ||
| "Call get_coverage first. It is free and returns the window, what is in it,", | ||
| "and a live mint that is guaranteed to have data. A token from outside the", | ||
| "window answers coverage=none, which is a real answer and is never charged —", | ||
| "check _meta.coverage before reading a body as a verdict.", | ||
| "", | ||
| "rug_probability_pct is a MEASURED collapse frequency for a calibrated band,", | ||
| "not an opinion and not a per-token risk starting at zero. The safest band", | ||
| "still rugged about 35% of the time and the universe base rate is 45%, so a", | ||
| "filter under ~35 matches nothing. get_scorecard publishes every band.", | ||
| "", | ||
| "Order that works: find_tokens for candidates, check_token on each,", | ||
| "inspect_token on the few that flag, token_identity at the decision point.", | ||
| "check_wallet vets a counterparty at any stage. can_i_exit before buying", | ||
| "anything you intend to sell.", | ||
| "", | ||
| "Prices are per call and stated in each description. Nothing is charged for", | ||
| "a failed call, an empty result, or a token we do not hold.", | ||
| ].join("\n"); | ||
| const server = new Server( | ||
| { name: "mindjack", version: PKG_VERSION }, | ||
| { capabilities: { tools: {} } } | ||
| { | ||
| capabilities: { tools: {}, resources: {}, prompts: {} }, | ||
| instructions: INSTRUCTIONS, | ||
| } | ||
| ); | ||
@@ -676,2 +796,13 @@ | ||
| const W1 = "bwamJzztZsepfkteWRChggmXuiiCQvpLqPietdNfSXa"; | ||
| const TOKEN_PAGE = "https://mindjack.xyz/token/"; | ||
| // base58 has no 0, O, I or l, and a Solana address is 32-44 of the rest. | ||
| const BASE58 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/; | ||
| // A link to the page for a mint, or nothing if that is not a mint. The value | ||
| // comes from our own mirror, so in practice it is always an address — this | ||
| // does not rely on that. The url is rendered by whichever client asked, and a | ||
| // mint carrying a quote or a space would reach that renderer inside an href we | ||
| // built. | ||
| const tokenUrl = (mint) => | ||
| BASE58.test(String(mint || "")) ? TOKEN_PAGE + encodeURIComponent(mint) : null; | ||
| const EXAMPLE_CALL = { | ||
@@ -698,2 +829,4 @@ check_token: { mint: M1 }, | ||
| search_tokens: { q: "bonk", days: 7 }, | ||
| search: { query: "bonk" }, | ||
| fetch: { id: M1 }, | ||
| }; | ||
@@ -708,2 +841,6 @@ | ||
| inputSchema: { ...inputSchema, examples: [EXAMPLE_CALL[name] || {}] }, | ||
| // Declaring this obliges us to return structuredContent that matches it, | ||
| // which the call handler below now does for every tool rather than for | ||
| // the two ChatGPT asked about. | ||
| ...(OUTPUT_SCHEMAS[name] ? { outputSchema: OUTPUT_SCHEMAS[name] } : {}), | ||
| annotations: READ_ONLY, | ||
@@ -720,3 +857,25 @@ })), | ||
| const result = await tool.run(req.params.arguments || {}); | ||
| return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; | ||
| // Modern clients read structuredContent and older ones read the text, so | ||
| // both carry the same value rather than one summarising the other. ChatGPT | ||
| // requires exactly this duplication for search and fetch. | ||
| // Both shapes, always. A tool that declares an outputSchema must return | ||
| // structuredContent, and the serialized text stays beside it because a | ||
| // client that predates structured output would otherwise get an empty | ||
| // answer. This used to be limited to search and fetch, which was the | ||
| // narrowest reading of "who asked for it": every tool declares a schema | ||
| // now, so every tool owes the structure. | ||
| const out = { | ||
| content: [{ type: "text", text: JSON.stringify(result, null, 2) }], | ||
| }; | ||
| // Not for an error body. A 402 or a refusal is a legitimate answer and it | ||
| // still goes back in full as text, but it shares no field with the shape | ||
| // this tool declared — and a new install has an empty key, so that is what | ||
| // twenty-two of these tools return on their very first call. Handing that | ||
| // back as `structuredContent` invites a client to read a payment challenge | ||
| // as the data it asked for. | ||
| if (result && typeof result === "object" && !Array.isArray(result) | ||
| && !result.error) { | ||
| out.structuredContent = result; | ||
| } | ||
| return out; | ||
| } catch (e) { | ||
@@ -730,3 +889,224 @@ return { | ||
| // --------------------------------------------------------------------------- | ||
| // Resources | ||
| // | ||
| // Four things a caller needs before it can read anything else: what the index | ||
| // holds, what the risk numbers were measured against, what each call costs, | ||
| // and one worked example. All four are free endpoints, so exposing them here | ||
| // costs nobody anything and saves an agent from spending a paid call to find | ||
| // out it was asking the wrong question. They are resources rather than tools | ||
| // because a client can attach them once and cache them, instead of the model | ||
| // having to decide to call them. | ||
| // --------------------------------------------------------------------------- | ||
| const RESOURCES = [ | ||
| { | ||
| uri: "mindjack://coverage", | ||
| name: "Index coverage", | ||
| description: | ||
| "What we hold and how fresh: the window, the platforms inside it, what " + | ||
| "is excluded, and a live mint guaranteed to have data. Read this before " + | ||
| "testing with a token of your own.", | ||
| mimeType: "application/json", | ||
| path: "/v1/coverage", | ||
| }, | ||
| { | ||
| uri: "mindjack://scorecard", | ||
| name: "Risk calibration", | ||
| description: | ||
| "Every calibrated band with the collapse rate MEASURED for it, its " + | ||
| "sample size and the window. This is what rug_probability_pct is read " + | ||
| "against; the safest band still rugs about 35% of the time.", | ||
| mimeType: "application/json", | ||
| path: "/v1/scorecard", | ||
| }, | ||
| { | ||
| uri: "mindjack://prices", | ||
| name: "Price list", | ||
| description: | ||
| "Every priced route with its price, the asset, the network and the " + | ||
| "receiving address. The same document an x402 client pays from.", | ||
| mimeType: "application/json", | ||
| path: "/.well-known/x402", | ||
| }, | ||
| { | ||
| uri: "mindjack://sample", | ||
| name: "Worked example", | ||
| description: | ||
| "One real token answered in full, free: the complete check, inspect and " + | ||
| "identity bodies with the real price of each. The shape of what you " + | ||
| "would be buying, before buying it.", | ||
| mimeType: "application/json", | ||
| path: "/v1/sample", | ||
| }, | ||
| ]; | ||
| server.setRequestHandler(ListResourcesRequestSchema, async () => ({ | ||
| resources: RESOURCES.map(({ uri, name, description, mimeType }) => ({ | ||
| uri, | ||
| name, | ||
| description, | ||
| mimeType, | ||
| })), | ||
| })); | ||
| server.setRequestHandler(ReadResourceRequestSchema, async (req) => { | ||
| const found = RESOURCES.find((r) => r.uri === req.params.uri); | ||
| if (!found) throw new Error("Unknown resource: " + req.params.uri); | ||
| const body = await call(found.path); | ||
| return { | ||
| contents: [ | ||
| { | ||
| uri: found.uri, | ||
| mimeType: found.mimeType, | ||
| text: JSON.stringify(body, null, 2), | ||
| }, | ||
| ], | ||
| }; | ||
| }); | ||
| // --------------------------------------------------------------------------- | ||
| // Prompts | ||
| // | ||
| // The orders of calls that are worth running, written down. Each one is the | ||
| // chain a reader would otherwise have had to derive from the whole tool list: | ||
| // which tool first, what to read out of it, and when the next one is worth its | ||
| // price. They take arguments rather than being static text, because a workflow | ||
| // you cannot point at a mint is a tutorial. | ||
| // --------------------------------------------------------------------------- | ||
| const PROMPTS = [ | ||
| { | ||
| name: "vet_before_buying", | ||
| description: | ||
| "The full check on one token before taking a position: structure first, " + | ||
| "then who is holding it, then whether it can be sold.", | ||
| arguments: [ | ||
| { name: "mint", description: "Solana mint address.", required: true }, | ||
| ], | ||
| build: (a) => | ||
| [ | ||
| "Decide whether to buy " + a.mint + ". Work in this order and stop", | ||
| "early if something disqualifies it.", | ||
| "", | ||
| "1. check_token on " + a.mint + '. If _meta.coverage is "none" we hold', | ||
| " nothing on it: say so and stop, because that is not a clean", | ||
| " verdict. Read rug_risk.verdict together with rug_risk.calibration,", | ||
| " since the probability is a MEASURED rate for that band and not an", | ||
| " opinion. Read collapse_speed too: a token that dies in sixty", | ||
| " seconds is a different risk from one that bleeds out over a day.", | ||
| "2. If it survives that, inspect_token on " + a.mint + " for who is", | ||
| " holding it: wallet groups, controlled supply, fresh wallets.", | ||
| "3. token_identity on " + a.mint + " at the decision point.", | ||
| " sibling_outcomes is the field that changes minds: it says how the", | ||
| " OTHER tokens these same wallets held ended up. A clean structure", | ||
| " with a cohort that rugged sixteen times out of twenty is not a", | ||
| " clean token.", | ||
| "4. can_i_exit on " + a.mint + " before committing. A position you", | ||
| " cannot sell at your size is not a position.", | ||
| "", | ||
| "Then state the decision, the number that drove it, and what would", | ||
| "change it. Do not predict a price; we do not publish one.", | ||
| ].join("\n"), | ||
| }, | ||
| { | ||
| name: "find_candidates", | ||
| description: | ||
| "Screen recent launches down to the few worth paying for depth on.", | ||
| arguments: [ | ||
| // MCP prompt arguments are strings on the wire, always. Saying so here | ||
| // is the difference between a client sending 6 and being refused by | ||
| // schema validation before the server sees it, and sending "6". | ||
| { | ||
| name: "hours", | ||
| description: "How far back to look, as a string. Default 24.", | ||
| required: false, | ||
| }, | ||
| { | ||
| name: "min_mcap", | ||
| description: "Minimum market cap in USD, as a string.", | ||
| required: false, | ||
| }, | ||
| ], | ||
| build: (a) => | ||
| [ | ||
| "Find launches worth a second look in the last " + | ||
| (a.hours || 24) + | ||
| " hours.", | ||
| "", | ||
| "1. find_tokens with hours=" + (a.hours || 24), | ||
| // null, not "": the filter below keeps deliberate blank lines and | ||
| // drops this one, so an unused optional argument leaves no gap. | ||
| a.min_mcap ? " and min_mcap=" + a.min_mcap : null, | ||
| " Set max_rug_pct no lower than 40. It is a measured band frequency", | ||
| " and the safest band is about 35%, so anything under that returns", | ||
| " nothing however wide you make the window.", | ||
| "2. Rank what comes back by action and rug_probability_pct, but look at", | ||
| " volume against market cap as well: the list is ordered by recency,", | ||
| " not by opportunity.", | ||
| "3. check_token the few that read clear. At a tenth of a cent it is", | ||
| " cheaper to check than to guess.", | ||
| "4. token_identity only on the survivors. It is the expensive call and", | ||
| " it is the one that changes the answer.", | ||
| "", | ||
| "Report the shortlist with the reason each one survived, and say", | ||
| "plainly if nothing did.", | ||
| ] | ||
| // Not filter(Boolean): the empty strings in this list are | ||
| // deliberate blank lines, and dropping them along with the | ||
| // unused optional line ran every paragraph together. | ||
| .filter((line) => line !== null) | ||
| .join("\n"), | ||
| }, | ||
| { | ||
| name: "vet_counterparty", | ||
| description: | ||
| "What a wallet has done before: the tokens it held, how they ended, and " + | ||
| "who funds it.", | ||
| arguments: [ | ||
| { name: "address", description: "Solana wallet address.", required: true }, | ||
| ], | ||
| build: (a) => | ||
| [ | ||
| "Build a picture of " + a.address + " before dealing with it.", | ||
| "", | ||
| "1. check_wallet on " + a.address + " for its record: what it has", | ||
| " held, how those ended, and whether it behaves like a sniper or an", | ||
| " insider.", | ||
| "2. wallet_network on " + a.address + " for who funds it and who it", | ||
| " moves with. A wallet is rarely alone, and the funder is usually", | ||
| " the identity.", | ||
| "3. If it appears in a token you are looking at, run token_identity on", | ||
| " that token as well and see whether this wallet is one of the", | ||
| " recurring names or an ordinary holder.", | ||
| "", | ||
| "Say what the wallet is, on the evidence, and how confident that is.", | ||
| ].join("\n"), | ||
| }, | ||
| ]; | ||
| server.setRequestHandler(ListPromptsRequestSchema, async () => ({ | ||
| prompts: PROMPTS.map(({ name, description, arguments: args }) => ({ | ||
| name, | ||
| description, | ||
| arguments: args, | ||
| })), | ||
| })); | ||
| server.setRequestHandler(GetPromptRequestSchema, async (req) => { | ||
| const found = PROMPTS.find((p) => p.name === req.params.name); | ||
| if (!found) throw new Error("Unknown prompt: " + req.params.name); | ||
| const args = req.params.arguments || {}; | ||
| for (const a of found.arguments || []) { | ||
| if (a.required && !args[a.name]) { | ||
| throw new Error("Prompt " + found.name + " needs " + a.name); | ||
| } | ||
| } | ||
| return { | ||
| description: found.description, | ||
| messages: [ | ||
| { role: "user", content: { type: "text", text: found.build(args) } }, | ||
| ], | ||
| }; | ||
| }); | ||
| await server.connect(new StdioServerTransport()); | ||
| process.stderr.write(`[mindjack] connected to ${BASE}\n`); |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
73365
111.03%5
25%1825
159.6%158
51.92%4
100%