Skip to content

MCP server: agentic fork-and-experiment over Stacks - #85

Merged
joaocosta9 merged 13 commits into
mainfrom
feat/mcp-server
Jul 25, 2026
Merged

MCP server: agentic fork-and-experiment over Stacks#85
joaocosta9 merged 13 commits into
mainfrom
feat/mcp-server

Conversation

@joaocosta9

Copy link
Copy Markdown
Contributor

What

Adds mcp/ — a TS MCP server (@ethui/stacks-mcp) that lets an agent drive Stacks: provision disposable forked anvil sandboxes, inspect the chain, deploy/simulate/execute, and use anvil cheatcodes. Every result carries an ethui explorer deep link so a human can verify. Full loop: provision → experiment → verify → tear down.

Tools (17, over stdio)

  • Lifecycle: create_stack / list_stacks / delete_stack
  • Read (+ explorer links): get_block / get_transaction (auto-decodes call + events) / get_address / get_logs
  • Deploy: deploy_contract from foundry out/
  • Experiment: simulate_call / execute (from impersonates any sender, no key)
  • Cheatcodes: impersonate / set_balance / mine / set_block_timestamp / snapshot / revert
  • Links: explorer_link for on-demand tx/address/block/root deep links

Notes

  • Talks to Stacks over HTTP like any client — no Elixir coupling; lives in the pnpm workspace next to frontend.
  • Auth: JWT (STACKS_TOKEN) for /stacks CRUD only. Per-stack key rides in the returned rpc url path, so viem + the browser self-auth.
  • ABI decode: foundry out/*.json, with an openchain/4byte fallback for contracts not in out/ (e.g. arbitrary mainnet on a fork).
  • Explorer links: match the explorer's own btoa(rpc) scheme; base64-of-a-URL is path-safe (verified). Route confirmed live (200).
  • pnpm --filter @ethui/stacks-mcp demo runs the fork-mainnet whale flow e2e (needs STACKS_TOKEN + FORK_URL).

Not included

  • Live e2e has been build- and unit-smoke-tested only; the real API run needs a token.
  • login tool / browser-loopback auth deferred (token-in-env for now).

🤖 Generated with Claude Code

joaocosta9 and others added 8 commits July 24, 2026 18:17
Add a TS MCP package to the workspace. Exposes create_stack/list_stacks/
delete_stack over stdio against the Stacks REST API, with base64 explorer
deep links built from each stack's key-in-url rpc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
get_block/get_transaction/get_address/get_logs resolve a stack's rpc via
GET /stacks/:slug and return bigint-safe data plus an explorer deep link.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
simulate_call dry-runs via eth_call. execute signs with PRIVATE_KEY or the
default anvil account 0 (no config needed), waits for the receipt, and returns
an explorer link.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- abi.ts loads foundry out/*.json into a merged ABI for calldata/event decode
- get_transaction now decodes call + events when FOUNDRY_OUT is set
- deploy_contract deploys bytecode from artifacts, returns address + link
- execute gains a 'from' param that impersonates any sender (no key)
- cheatcodes: impersonate, set_balance, mine, set_block_timestamp,
  snapshot, revert via raw anvil_/evm_ rpc

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When a selector isn't in local artifacts, resolve the signature via the
openchain database and decode args from it. Lets get_transaction decode
arbitrary mainnet contracts on a fork.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pnpm demo forks mainnet into a stack, impersonates a USDC whale, transfers
to anvil account 0, decodes the tx, prints explorer links, tears down.
Doubles as an e2e test (needs STACKS_TOKEN + FORK_URL).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Build a tx/address/block/root explorer link for a stack without a fetch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stacks Ready Ready Preview, Comment Jul 25, 2026 12:19pm

Request Review

The create/show urls omit the per-stack key until provisioning catches up
(load-dependent, seconds to over a minute), and show returns no urls at all
right after create. Build the keyed url from slug + baseHost + the api-key
token (populated immediately), then poll eth_chainId until anvil answers.

Verified end-to-end against hosted Stacks: create -> get_block -> execute
(anvil account 0) -> get_transaction -> delete.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
list/show return http_rpc/ws_rpc/explorer at top level while create nests
them under `urls`. Normalize both so list_stacks shows rpc + explorer links.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Serves MCP over streamable HTTP at /mcp on the api host, so tools run
inside the app instead of calling its REST api from outside. Chain calls
reach anvil on its process-local port, skipping the reverse proxy and the
per-stack api key.

Auth reuses the REST bearer JWT. Stacks owned by another user read as
missing rather than forbidden, to avoid leaking slugs.

ABI encoding and decoding stay out of scope: calldata goes in raw, so the
caller keeps using cast, viem, or whatever it already has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Booting a forked anvil outlasts a default GenServer call timeout, so
`ensure_running` takes an explicit one and `anvil_url` catches the exit
rather than letting callers crash. This covers the proxy path too.

Explorer block links carry decimal numbers, matching how the frontend
builds them; a block without a number links to the stack root instead of
a dangling path.

`EXPLORER_BASE` moves to runtime config, where the rest of the env-driven
settings live — read from config.exs it was baked in at build time.

Sessions now need a bearer token before one is spawned, so an anonymous
caller can no longer hold session processes on the public endpoint. The
MCP pipeline skips `accepts`, since clients negotiate json and
text/event-stream on the same path.

Also: roll back the stack row when its anvil fails to boot, default
`get_logs` from the fork block instead of scanning upstream history, widen
the receipt window, and preload api keys when listing without a user so
rpc urls keep their token.

The HTTP test now dispatches through the endpoint. Driving the plug
directly skipped the router, so it could not have caught the auth gate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bad fork options kill anvil outright, and the call to read its url then
exits with `:normal` rather than a timeout, which the earlier catch missed.
The agent saw "Tool execution crashed" instead of something it could act
on.

Found by running a mainnet fork locally: a fork_block_number ahead of the
chain head made anvil exit 1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@joaocosta9
joaocosta9 merged commit d97b8ed into main Jul 25, 2026
2 checks passed
@joaocosta9
joaocosta9 deleted the feat/mcp-server branch July 25, 2026 12:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant