Skip to content

feat: add MCP server for AI agent integration#21

Open
omarespejel wants to merge 37 commits intokeep-starknet-strange:mainfrom
omarespejel:feat/mcp-server
Open

feat: add MCP server for AI agent integration#21
omarespejel wants to merge 37 commits intokeep-starknet-strange:mainfrom
omarespejel:feat/mcp-server

Conversation

@omarespejel
Copy link

Summary

Adds packages/mcp-server — a Model Context Protocol server that exposes x SDK wallet operations as MCP tools. Any MCP-compatible client (Claude, Cursor, OpenAI Agents SDK, etc.) can now discover and invoke Starknet operations through a standard protocol.

This follows the pattern established by Stripe, Coinbase, and Alchemy: the SDK owner ships and maintains the MCP server rather than having agent frameworks wrap the SDK externally.

Tools (11 total)

Tool Type Description
x_get_balance read-only Check ERC20 token balance
x_transfer state-changing Send tokens (batched, amount-capped)
x_execute state-changing Raw contract calls (disabled by default)
x_deploy_account state-changing Deploy account on-chain
x_enter_pool state-changing Enter a staking pool
x_add_to_pool state-changing Add to existing stake
x_claim_rewards state-changing Claim staking rewards
x_exit_pool_intent state-changing Start exit from pool
x_exit_pool state-changing Complete pool exit
x_get_pool_position read-only Query staking position
x_estimate_fee read-only Estimate gas for calls

Security model

  • x_execute disabled by default — opt-in via --enable-execute flag
  • Per-transfer amount cap--max-transfer (default: 1000 tokens)
  • Batch limits — max 20 transfers, max 10 calls
  • Address validation — all addresses validated via fromAddress()
  • Runtime input validation — every tool argument validated with zod schemas
  • Transaction timeout — 2-minute deadline on tx.wait() to prevent hangs
  • Token allowlist — only pre-verified token presets accepted
  • Stdio transport only — no remote HTTP exposure by default

Testing

End-to-end tested against Sepolia testnet — 38/38 checks pass:

  • Read-only tools return live on-chain data
  • All validation gates verified (address, amount, cap, batch, execute gate)
  • Error paths return structured MCP error responses (no crashes)
  • Staking tools properly require config
  • TypeScript type check passes (tsc --noEmit)
  • Prettier + ESLint pass

Quick start

STARKNET_PRIVATE_KEY=0x... npx @keep-starknet-strange/x-mcp --network mainnet

Files

  • packages/mcp-server/src/index.ts — server implementation (879 lines)
  • packages/mcp-server/README.md — docs, security model, examples
  • packages/mcp-server/package.json — deps: @modelcontextprotocol/sdk, zod
  • packages/mcp-server/tsup.config.ts — build config (bundles x SDK)
  • packages/mcp-server/tsconfig.json — TypeScript config

Related

Test plan

  • npm run build succeeds
  • tsc --noEmit — zero type errors
  • MCP initialize handshake returns correct server info
  • tools/list returns 10 tools (11 with --enable-execute)
  • x_get_balance returns live balance from Sepolia RPC
  • Token resolution works (symbol, case-insensitive, rejects unknown)
  • Address validation rejects malformed addresses
  • Amount validation rejects negative values
  • Transfer cap enforced at 1000 default
  • Batch limit enforced at 20 transfers
  • x_execute blocked without --enable-execute
  • Staking tools require STARKNET_STAKING_CONTRACT
  • Missing required args return Validation error
  • RPC errors surface as structured MCP errors
  • Extra args are tolerated (passthrough)

Made with Cursor

Adds `packages/mcp-server` — a Model Context Protocol server that
exposes x SDK wallet operations as MCP tools.  Any MCP-compatible
client (Claude, Cursor, OpenAI Agents SDK, etc.) can now discover
and invoke Starknet operations through a standard protocol.

11 tools: get_balance, transfer, execute, deploy_account,
enter_pool, add_to_pool, claim_rewards, exit_pool_intent,
exit_pool, get_pool_position, estimate_fee.

Security:
- x_execute disabled by default (opt-in via --enable-execute)
- Per-transfer amount cap (--max-transfer, default 1000)
- Batch limits (20 transfers, 10 calls)
- All addresses validated via x SDK's fromAddress()
- Runtime argument validation with zod schemas
- Transaction confirmation timeout (2 min)
- Only pre-verified token presets accepted
- Stdio transport only (no remote exposure)

Tested end-to-end against Sepolia (38/38 checks pass):
read-only tools return live data, all validation and security
gates verified, error paths return structured MCP responses.

Co-authored-by: Cursor <cursoragent@cursor.com>
@0xLucqs
Copy link
Contributor

0xLucqs commented Feb 12, 2026

Thanks for the MCP server addition — strong direction overall. A few blocking points before merge for independent npm publishing:

  1. Packaging blocker (independent publish)
  • packages/mcp-server/package.json currently uses "x": "file:../../".
  • This will break npx @keep-starknet-strange/x-mcp for external users because that local path does not exist outside this repo.
  • Recommendation: switch to a published SDK package dependency (e.g. @keep-starknet-strange/x) and pin/test a compatible semver range.
  1. Security model mismatch
  • Header/docs imply state-changing tools require confirmation by default, but only x_execute is gated.
  • x_transfer, x_deploy_account, and staking write tools are currently enabled by default.
  • Recommendation: add a global --enable-write gate (keep read-only tools always enabled), and keep x_execute as an additional stricter gate.
  1. Risk cap coverage gap
  • --max-transfer cap only applies to x_transfer; staking amount tools (x_enter_pool, x_add_to_pool, x_exit_pool_intent) are uncapped.
  • Recommendation: either apply a write amount cap consistently or document clearly why staking is intentionally uncapped.
  1. CLI robustness
  • --network is cast to mainnet|sepolia without validation. Invalid values fail later with less-clear errors.
  • Recommendation: validate the flag early and fail fast with a clear message.

If you want, I can open a follow-up PR that implements these with minimal API churn.

…n, dep fix

- Add --enable-write flag: all state-changing tools disabled by default,
  read-only tools (balance, pool position, estimate fee) always available
- Apply --max-amount cap consistently to transfers AND staking operations
  (enter_pool, add_to_pool, exit_pool_intent), renamed from --max-transfer
- Validate --network flag at startup with clear error for invalid values
- Move x SDK to devDependencies: tsup bundles it at build time via
  noExternal, so consumers don't need it at runtime (avoids installing
  the wrong 'x' package from npm)
- Add runtime defense-in-depth: write tools are blocked even if a client
  calls them by name directly, bypassing tool listing
- Add staking config guard: clear error when staking tools called without
  STARKNET_STAKING_CONTRACT env var
- Update README: security model, CLI args table, config examples, checklist

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@omarespejel omarespejel requested a review from 0xLucqs February 18, 2026 03:43
@omarespejel
Copy link
Author

omarespejel commented Feb 18, 2026

(Edited) This comment was mangled by shell quoting; please ignore. See the corrected reply here: #21 (comment)

@omarespejel
Copy link
Author

Addressed all 4 blockers in commit ffd113d:

  1. Packaging
  • packages/mcp-server/tsup.config.ts bundles the local SDK into the output via noExternal: ["x"].
  • packages/mcp-server/package.json keeps x as a devDependency for local builds. At runtime the server doesn't rely on resolving x from npm or file:../../.
  1. Security gating
  • All state-changing tools are disabled unless --enable-write is provided.
  • x_execute additionally requires --enable-execute.
  • Runtime checks enforce this even if a client calls tools by name.
  1. Cap coverage
  • --max-amount cap is enforced across transfers and staking amount-bearing ops (x_enter_pool, x_add_to_pool, x_exit_pool_intent).
  1. CLI robustness
  • --network is validated early and fails fast with a clear error.

Can you re-review when you get a chance?

@0xLucqs
Copy link
Contributor

0xLucqs commented Feb 19, 2026

Thanks for the MCP server work — a few concrete suggestions:

  1. Staking amount parsing should be pool-token-derived (or validated), not caller-token-derived.
  • In x_enter_pool, x_add_to_pool, and x_exit_pool_intent, optional token can cause decimals mismatch.
  • Suggestion: remove token from those tool schemas, resolve pool token from chain by pool address, and parse amount with that token.
  • Backward-compatible option: keep token but reject if it doesn’t match the resolved pool token.
  1. Package publishability.
  • packages/mcp-server/package.json uses "x": "file:../../" while README suggests npx @keep-starknet-strange/x-mcp.
  • Suggestion: switch to a published dependency (or clearly document workspace-only usage).
  1. Staking tool exposure should match config availability.
  • Staking tools are listed even when staking config is missing, which leads to runtime errors.
  • Suggestion: hide/disable staking tools unless staking config is present, or fail fast at startup with a clear message.

Optional tests:

  • Reject staking token mismatch
  • Ensure staking tools are hidden/disabled without staking config
  • Smoke test published install path

@0xLucqs
Copy link
Contributor

0xLucqs commented Feb 26, 2026

handleTool currently applies rate limits before validating whether the requested tool exists (packages/mcp-server/src/index.ts:1136).

Why this matters

Current order:

  1. await runRateLimitChecks(name)
  2. Resolve schema by tool name
  3. If missing, throw Unknown tool

Unknown tool names are not in READ_ONLY_TOOLS, so they are treated as write calls and charged to writeRequestTimestamps.
This means typos/probing can consume --write-rate-limit-rpm and block legitimate write operations.

Suggested fix

  • Validate tool existence first.
  • Only apply read/write bucket limits for known tools.
  • If unknown calls should still be throttled, count them in a separate/global bucket (not the write bucket).

Minimal patch direction

const schema = schemas[name as keyof typeof schemas];
if (!schema) {
  throw new Error(`Unknown tool: ${name}`);
}
await runRateLimitChecks(name);

Regression test idea
Configure a low write RPM.
Send several unknown tool calls.
Verify a valid write tool call still succeeds (unknown calls should not consume write quota).

@omarespejel omarespejel marked this pull request as draft February 27, 2026 11:05
@omarespejel omarespejel marked this pull request as ready for review February 27, 2026 12:53
@omarespejel
Copy link
Author

omarespejel commented Feb 27, 2026

Thanks @0xLucqs! corrected that and run a harder testnet test

Testnet validation update (Sepolia):

I ran a full MCP E2E matrix on Sepolia against this branch using:

  • RPC: https://starknet-sepolia-rpc.publicnode.com
  • Paymaster: https://sepolia.paymaster.avnu.fi + x-paymaster-api-key
  • Staking contract: 0x03745ab04a431fc02871a139be6b93d9260b0ff3e779ad9c8b377183b23109f1
  • Pool: 0x056e375b0554d472f125d5de8f7d20994167d69ec4c5ebf16c5de17cac2818c8
  • Test account: 0x07ee2bbb36ddac2f783c7d595f7bc0d6639869ade055db5e8884eeeaae361e14

Write-paths confirmed working:

  • starkzap_transfer (sponsored):
    • 0x029c6d6e41279508ff02cf8c427595bdc4a03ef040e39817d0808b9a1d45b82b
  • starkzap_execute (sponsored, zero self-transfer):
    • 0x02f8e62d0e01bc7a64046985c105202ef1f8b8e92cd61e64ae7a64f7a8fa8edb
  • starkzap_add_to_pool:
    • 0x0295095618db4d24f8abf21a95e53d710714e6415b5be354e379eac356b3af35
  • starkzap_exit_pool:
    • 0x02162c8711552a9398569fcde44e2be7895816a0f391efa2a777158058d097d7

Read/validation paths also passed:

  • starkzap_get_account
  • starkzap_get_balance
  • starkzap_get_pool_position

State-dependent expected rejections were observed correctly:

  • starkzap_enter_pool rejected when already a pool member
  • starkzap_exit_pool_intent rejected when already in exit process
  • starkzap_claim_rewards rejected when rewards were zero

So the MCP flow is working in practice on testnet (not only unit/integration gates), including sponsored tx and staking operations

@0xLucqs
Copy link
Contributor

0xLucqs commented Feb 27, 2026

Reviewed against current PR head dbb2292.

I found two issues that look real and reproducible:

  1. Error sanitization can leak upstream details in user-visible tool errors
  • In buildToolErrorText, messages starting with "Could " are returned verbatim.
  • resolvePoolTokenForOperation throws "Could not resolve staking pool metadata ... ${reason}", and reason may include internal RPC host/URL text from upstream errors.
  • This can expose infrastructure details to MCP clients instead of returning a redacted reference.
  • References:
    • packages/mcp-server/src/index.ts (buildToolErrorText, safe-prefix allowlist)
    • packages/mcp-server/src/index.ts (resolvePoolTokenForOperation catch/rethrow)
  1. IPv6 localhost HTTP URL is incorrectly rejected by secure URL validation
  • isSecureRpcUrl allows HTTP for localhost and checks hostname === "::1".
  • For URLs like http://[::1]:8545, Node URL parsing returns hostname "[::1]" (with brackets), so valid localhost endpoints are rejected.
  • This affects both RPC and paymaster URL validation paths.
  • Reference: packages/mcp-server/src/index.ts (isSecureRpcUrl)

I intentionally excluded earlier notes that were less certain.

@omarespejel omarespejel marked this pull request as draft March 2, 2026 13:02
@omarespejel omarespejel marked this pull request as ready for review March 2, 2026 18:58
@omarespejel
Copy link
Author

omarespejel commented Mar 2, 2026

Thanks @0xLucqs ! Applied the feedback and added additional tests and improvements

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.

2 participants