{
  "markdown": "# uniswap-tx-builder-mcp\n\n[![CI](https://github.com/Yummybait-fin/uniswap-tx-builder-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Yummybait-fin/uniswap-tx-builder-mcp/actions/workflows/ci.yml)\n[![coverage](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2FYummybait-fin%2Funiswap-tx-builder-mcp%2Fbadges%2Fcoverage.json)](https://github.com/Yummybait-fin/uniswap-tx-builder-mcp/actions/workflows/coverage-badge.yml)\n[![npm version](https://img.shields.io/npm/v/%40yummybait%2Funiswap-tx-builder-mcp)](https://www.npmjs.com/package/@yummybait/uniswap-tx-builder-mcp)\n[![node](https://img.shields.io/node/v/%40yummybait%2Funiswap-tx-builder-mcp)](https://www.npmjs.com/package/@yummybait/uniswap-tx-builder-mcp)\n[![license](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/Yummybait-fin/uniswap-tx-builder-mcp/badge)](https://scorecard.dev/viewer/?uri=github.com/Yummybait-fin/uniswap-tx-builder-mcp)\n\nA **keyless** [MCP](https://modelcontextprotocol.io) server that builds *unsigned* Uniswap v3\nliquidity-position transactions and optionally simulates them via `eth_call`. **It never holds\nkeys and never signs** — you take the returned calldata and sign + broadcast it with your own\nwallet (viem, a CDP wallet MCP, any signer).\n\nBecause it's keyless, the only limits that apply to a built tx are *your wallet's* — the server's\nthreat surface is just \"it returns calldata and reads public RPCs.\"\n\n## Ecosystem\n\nPart of [Yummybait](https://www.yummybait.finance/):\n\n- [`cdp-wallet-agent-example`](https://github.com/Yummybait-fin/cdp-wallet-agent-example) — a\n  reference agent that drives this MCP with a Coinbase CDP wallet as the signer.\n- [yummybait.finance](https://www.yummybait.finance/) — the product these tools power.\n\n## Tools\n\nEvery build tool returns `tx = { to, data, value, chainId }` plus `rlp` — the **unsigned\nEIP-1559 (type-2)** serialization of `tx` with nonce/fees/gas zeroed (signing services like the\nCDP API populate them; serialize `tx` yourself if you manage nonces) — and a human `description`.\n`value` is `\"0\"` except the payable Universal Router wrap/swap builds. Addresses are `0x…40`;\n`positionId` and amounts are decimal **strings** (they exceed JS safe integers).\n\n| Tool | Purpose | Needs RPC? |\n|------|---------|------------|\n| `build_collect` | Collect all uncollected fees from a position to `recipient`. | Only for the dry-run (on by default; `simulate: false` builds offline) |\n| `build_close` | Remove all liquidity **+** collect; `burn: true` also burns the empty NFT. Returns the read position. | **Always** — reads the position first |\n| `build_mint` | Mint a new position (raw ticks + wei amounts). | Only with `simulate: true` |\n| `build_increase` | Add liquidity to an existing position. | Only with `simulate: true` |\n| `build_approve` | Build an ERC-20 `approve(spender, amount)` tx — e.g. the NFPM before `build_mint`/`build_increase`, or Permit2 before a Permit2-paid `build_swap`. `amount: \"max\"` for an unlimited allowance. | Only with `sender` (enables the dry-run) |\n| `build_wrap` | Wrap native ETH → WETH via the Universal Router (`WRAP_ETH`). | Only with `sender` (enables the dry-run) |\n| `build_swap` | Exact-in single-hop `tokenIn` → `tokenOut` swap via the Universal Router; with `wrapWei` (tokenIn = WETH9) it wraps native ETH first and sweeps the remainder, with `unwrapOut` (tokenOut = WETH9) it unwraps the output to native ETH — in the same tx either way. Pass `sender` and the default Permit2 payment path auto-detects an insufficient allowance, returning a signed-permit request instead of a tx — no separate on-chain Permit2 approval needed. | Only with `sender` (enables the dry-run) |\n| `get_swap_quote` | **Read-only.** Quote an exact-in single-hop swap via Uniswap v3 QuoterV2 — the live `amountOut` a `build_swap` with the same params would get, plus `amountOutMin` (after `slippageBps`) ready to feed straight into `build_swap`. | **Always** |\n| `plan_position` | **Read-only.** Turn a human price range + human amounts into aligned ticks + wei amounts for `build_mint`. Reads token decimals over RPC. | **Always** |\n| `get_pool_state` | **Read-only.** Live pool state (tick, sqrtPriceX96, human price, spacing); optional ±pct range suggestion (rounded inward) and live-ratio `amount0Desired`/`amount1Desired` from wallet balances. | **Always** |\n| `get_positions` | **Read-only.** List every position NFT a wallet holds (token0/1, fee, tick range, liquidity, tokens owed) via the NFPM's ERC-721 enumeration. | **Always** |\n\nEncoding itself is **offline**: `build_mint`, `build_increase`, `build_wrap`, `build_swap`, and\n`build_collect` (with `simulate: false`) produce calldata without any network access. Only chain\n*reads* need a reachable RPC endpoint — and public defaults are baked in per chain, so nothing has\nto be configured either way (see [Configuration](#configuration) to override them).\n\n`simulate` runs an opt-in `eth_call` dry-run: **on by default** for collect/close, **off** for\nmint/increase (those need approvals + balances, so the dry-run usually reverts); wrap/swap/approve\nsimulate when you pass `sender` (the signing wallet). A reverted simulation comes back as an\nerror — don't sign a tx that failed to simulate. When a dry-run succeeds, the response also\nincludes `simulationResult` — the actual decoded return value of the call (e.g.\n`{amount0, amount1}` for `build_collect`/`build_close`, `{tokenId, liquidity, amount0, amount1}`\nfor `build_mint`, `{approved}` for `build_approve`) rather than just the `simulated: true` flag.\n`build_wrap`/`build_swap` go through the Universal Router's `execute`, which has no return value,\nso they only report `simulated`. See the companion skill for the full argument reference and\nposition lifecycle.\n\n## Install & run\n\nFrom npm (no clone, stdio transport — what MCP clients spawn):\n\n```bash\nnpx -y @yummybait/uniswap-tx-builder-mcp\n```\n\nFrom source:\n\n```bash\nnpm install\nnpm run dev                 # stdio MCP from source via tsx\nnpm run build && npm start  # compile to dist/, then run the built server\nnpm test                    # vitest\n```\n\nSet `MCP_HTTP_PORT` to serve the streamable-HTTP transport instead of stdio (endpoint:\n`http://<host>:<port>/mcp`). HTTP runs **stateless** — every POST gets a fresh server/transport\npair, so any number of clients can connect and reconnect freely with no session bookkeeping:\n\n```bash\nMCP_HTTP_PORT=8102 npm run dev\n```\n\nDocker — build locally, or pull a released image from GHCR:\n\n```bash\ndocker build -t uniswap-tx-builder-mcp:local .                        # local build\ndocker pull ghcr.io/yummybait-fin/uniswap-tx-builder-mcp:latest       # released image\n\ndocker run -i --rm uniswap-tx-builder-mcp:local                       # stdio\ndocker run --rm -p 8102:8102 -e MCP_HTTP_PORT=8102 uniswap-tx-builder-mcp:local  # HTTP\n```\n\n## Connect to an MCP client\n\n**Claude Code** (stdio via npm):\n\n```bash\nclaude mcp add uniswap-tx-builder -- npx -y @yummybait/uniswap-tx-builder-mcp\n```\n\n**Generic client config** (Claude Desktop, etc.) — add to the client's `mcpServers`:\n\n```json\n{\n  \"mcpServers\": {\n    \"uniswap-tx-builder\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@yummybait/uniswap-tx-builder-mcp\"],\n      \"env\": { \"RPC_ETH\": \"https://your-eth-rpc\" }\n    }\n  }\n}\n```\n\n(For a local build, swap the command for `node /abs/path/to/uniswap-tx-builder-mcp/dist/mcp.js`.)\n\nFor HTTP, run the server with `MCP_HTTP_PORT` and point the client at `http://<host>:<port>/mcp`.\n\n## Install the companion skill\n\n`skills/uniswap-tx-builder/` is a generic agent **skill** (no app- or wallet-specific knowledge)\nthat teaches an agent how to drive these tools: the argument reference, simulate-first, the\nclose→mint rebalance sequence, and the \"your wallet signs\" handoff. It pairs with the MCP — install\nboth. Pick whichever install path suits you.\n\n**A. Claude Code plugin (`/plugin`)** — the repo doubles as a plugin marketplace:\n\n```text\n/plugin marketplace add Yummybait-fin/uniswap-tx-builder-mcp\n/plugin install uniswap-tx-builder@yummybait\n```\n\n**B. npx** — copies the skill into a skills dir (no clone needed):\n\n```bash\n# personal (~/.claude/skills, every project)\nnpx -p @yummybait/uniswap-tx-builder-mcp uniswap-tx-builder-skill\n\n# or project-scoped (./.claude/skills, checked in with a repo)\nnpx -p @yummybait/uniswap-tx-builder-mcp uniswap-tx-builder-skill --project\n```\n\n**C. Manual copy** — straight from a checkout:\n\n```bash\ncp -r skills/uniswap-tx-builder ~/.claude/skills/                       # personal\nmkdir -p .claude/skills && cp -r skills/uniswap-tx-builder .claude/skills/  # project\n```\n\nThe agent picks it up by its `SKILL.md` frontmatter — no restart needed for project skills.\n\n## Configuration\n\nPer-chain RPCs default to public endpoints; override with env vars (see `src/config.ts`):\n\n| Chain | ID | RPC env var |\n|-------|----|-------------|\n| Ethereum | 1 | `RPC_ETH` |\n| Optimism | 10 | `RPC_OP` |\n| Polygon | 137 | `RPC_POLYGON` |\n| Base | 8453 | `RPC_BASE` |\n| Arbitrum | 42161 | `RPC_ARB` |\n\nPublic RPCs are rate-limited and best-effort — set your own for anything beyond casual use.\n\n## Architecture\n\nOne code path, transport kept separate so it stays testable and ready for a future v4 tool set:\n\n```\nbuilder.ts     calldata + unsigned-RLP encoding (viem), position/pool reads\nticks.ts       pure tick / sqrt-price / liquidity math (no I/O)\noperations.ts  build + optional eth_call simulate + response shaping\nserver.ts      the MCP tool surface (schemas, registration, logging)\nmcp.ts         transport bootstrap (stdio / stateless streamable HTTP)\n```\n\n## CI / releases\n\nGitHub Actions (`.github/workflows/`):\n\n- **CI** — typecheck + tests + npm-tarball allowlist check on every push to `main` and on PRs.\n- **Release** — pushing a `v*` tag re-runs the tests, bumps the version on `main` to match the tag\n  (`package.json` + `.claude-plugin/plugin.json`), publishes the npm package\n  (`@yummybait/uniswap-tx-builder-mcp`) **with provenance**, and builds + publishes the Docker\n  image to GHCR (`ghcr.io/yummybait-fin/uniswap-tx-builder-mcp`), tagged with the version (and\n  `latest`). The tag is the single version source for both artifacts; the bump lands on `main`\n  after the tag, so the tagged commit keeps its old version.\n\n```bash\ngit tag v0.3.0 && git push origin v0.3.0   # cut a release\n```\n\n### npm supply-chain posture\n\nThe npm publish job is locked down; keep these properties when touching it:\n\n- **Trusted publishing (OIDC)** — no long-lived npm token in CI. Configured on npmjs.com under\n  *package → Settings → Trusted publisher* (GitHub Actions, this repo, `release.yml`). The\n  `NPM_TOKEN` secret path in the workflow exists **only to bootstrap the first release** (trusted\n  publishers can't be configured before the package exists) — delete the secret afterwards and\n  set the package's publishing access to *\"Require two-factor authentication and disallow\n  tokens\"*.\n- **Provenance** — `publishConfig.provenance: true` attaches a Sigstore attestation linking every\n  published tarball to the exact workflow run and commit. It also makes an accidental local\n  `npm publish` fail (no OIDC outside CI). Consumers verify with `npm audit signatures`.\n- **Gates before publish** — `npm audit signatures` (registry attestations of the dep tree),\n  `npm audit --omit=dev --audit-level=high` (no known high/critical vulns in shipped deps),\n  typecheck + full test suite (`prepublishOnly`), and `scripts/verify-tarball.mjs` (the tarball\n  must contain exactly the allowlisted files — no secrets, no strays).\n- **Hardened job** — `npm ci --ignore-scripts` (no dependency postinstall runs where publish\n  credentials live), minimal per-job permissions, actions pinned to commit SHAs, Dependabot\n  keeping pins and deps fresh.\n\n## Scope\n\nUniswap v3 `NonfungiblePositionManager` on the chains above. Calling an unconfigured chain returns\nan \"Unsupported chain\" error. Roadmap: Uniswap v4 as a separate set of tools alongside these.\n",
  "bytes": 12090,
  "sha": "1c8a8b6c9a2764fe5e538f6875aa4407126838c9f03fd5b6b7774bf2a7c1188a",
  "repo_slug": "yummybait-fin/uniswap-tx-builder-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_yummybait_fin_uniswap_tx_build_05ea12f9/readme"
}