tokensift
Official# tokensift
Read-only token due-diligence over keyless public APIs, as one Python package that is both an importable library and a stdio MCP server. Six tools, one envelope, no trading, no secrets.
| Tool | Upstream | Chains |
|---|---|---|
| `honeypot_check` | honeypot.is buy/sell simulation | ethereum, bsc, base |
| `rugcheck_report` | rugcheck.xyz risk report | solana |
| `dex_search` | DexScreener search | all |
| `dex_token_pairs` | DexScreener pools for a token | ethereum, bsc, base, solana |
| `dex_pair` | DexScreener pair snapshot | ethereum, bsc, base, solana |
| `jupiter_prices` | Jupiter Price v3 (lite host) | solana |
Every call returns the same envelope (also each tool's `outputSchema`):
```json
{"ok": true, "source": {"provider": "dexscreener", "url": "…exact GET…", "chain": "solana"},
"fetched_at": "2026-09-06T12:00:00Z", "cache": {"hit": false, "ttl_s": 30},
"summary": {…}, "raw": null, "raw_truncated": false, "error": null}
```
On failure `ok` is false, `summary` is null, and `error` carries `type` (`invalid_input | not_found | rate_limited | upstream_unavailable | upstream_changed`), `message`, optional `retry_after_s`, and `advice` written for the model. The MCP tool returns the same envelope with `isError=true`.
## Library
```python
from tokensift import Tokensift
async with Tokensift() as ts:
env = await ts.honeypot_check("base", "0x…")
if env.ok:
print(env.summary["is_honeypot"], env.summary["sell_tax"])
```
## MCP server
```bash
uvx --from /path/to/tokensift tokensift-mcp # stdio
```
See `docs/REGISTRATION.md` for Claude Code, Codex CLI, and Gemini CLI entries and the Inspector smoke test.
## Behavior that matters
- Validation before network: chain aliases (`eth`, `1`, `bnb`, `56`, `8453`, `sol`) and address formats are checked first; bad input never hits an upstream.
- Per-provider token buckets (`TOKENSIFT_RPM_*`). When a bucket is empty the call waits up to `TOKENSIFT_LIMITER_WAIT_S` (5 s) and then returns `rate_limited` with `retry_after_s`. `rate_limited` and `upstream_unavailable` are never cached; `invalid_input` and `not_found` are cached for `TOKENSIFT_NEG_CACHE_TTL_S` (10 s).
- Successful responses are cached for `TOKENSIFT_CACHE_TTL_S` (30 s) keyed by provider, endpoint, and normalized params.
- One retry on 429/502/503/504 honoring `Retry-After`, capped at 5 s. Timeouts and 401/403 become `upstream_unavailable` (403 advice names the datacenter-IP possibility).
- `raw` is returned only with `verbose=true`, capped at `TOKENSIFT_RAW_MAX_BYTES` (32 kB) with `raw_truncated=true` when cut.
- Logging goes to stderr only; stdout is the MCP wire.
- Dual-era: the server answers both a legacy `initialize` handshake and modern `2026-07-28` per-request metadata (tested over a real pipe).
## Development
```bash
uv sync
uv run pytest # unit + client + server + contract + stdio (no network)
uv run pytest -m live # one real call per upstream; run weekly
uv run python scripts/record_fixtures.py # refresh recorded upstream fixtures
npx -y @modelcontextprotocol/inspector --cli ./scripts/serve-stdio.sh --method tools/list # flag-free launcher: the inspector cli eats `--from`
```
Stack (pinned 2026-09-06): `fastmcp>=4.0,<4.1` on `mcp` 2.x (spec `2026-07-28`), `httpx2`, `pydantic-settings`, `aiolimiter`, `cachetools`. Python 3.12 via `uv`.
### T0 spike findings (2026-09-06)
- `fastmcp.tools.ToolResult(content, structured_content, meta, is_error)` accepts `is_error` with structured content, so errors are returned as results, never raised (a raised exception becomes a JSON-RPC error in `mcp` 2.x).
- `mcp.server.runner` drives `serve_dual_era_loop`; a legacy `initialize` is handled inline and negotiated from `HANDSHAKE_PROTOCOL_VERSIONS`. `tests/test_stdio.py` proves both handshakes over a pipe.
- The `Envelope` field `ok` forbids a classmethod of the same name; factories are `Envelope.success` / `Envelope.failed`.
- MCP Inspector CLI v2 parses flags anywhere on its command line, so a target like `uvx --from … tokensift-mcp` loses `--from` and reports "Connection closed". `scripts/serve-stdio.sh` is a flag-free launcher for the Inspector; the three CLIs pass args through correctly and use `uvx` directly.
TDQS
Scored across 6 tools
Tools are mostly distinct: honeypot_check and rugcheck_report clearly separate EVM vs Solana safety checks; dex_search, dex_token_pairs, and dex_pair cover different granularities of pair data. Minor overlap exists between dex_search and dex_token_pairs, but descriptions clarify when each is appropriate.
All tool names use consistent snake_case and follow a predictable pattern: domain-specific prefix (dex_, jupiter_, honeypot_, rugcheck_) plus a clear noun or verb. The naming scheme makes the tool's function obvious and uniform across the set.
Six tools is a well-scoped size for a token research and safety-checking server. Each tool covers a distinct aspect of the workflow (search, pair lookup, pair snapshot, safety verification, pricing) without redundancy or bloat.
The core workflow (find token, verify pair, assess safety, get price) is covered. Minor gaps exist: no EVM-specific price source analogous to jupiter_prices, and dex_token_pairs only queries one chain at a time, requiring multiple calls for cross-chain analysis.