Skip to content

Conversation

zees-dev
Copy link
Contributor

Description

Introducing Forc-MCP module (CLI).

The MCP server can be run in 3 modes:

  • stdio
  • sse: long-running server - using http server-side-events
  • http: long-running server - using http streams

Forc-call integration

The first tool to be integrated is forc-call - which exposes the following tool calls:

  • list_contract_functions
  • transfer_assets
  • get_execution_trace
  • call_contract

And the following resources:

  • MCP Type Encoding Reference
  • MCP Tool Usage Examples
  • Contract Examples with MCP Tools

Screenshots from @modelcontextprotocol/inspector

Screenshot 2025-07-15 at 5 09 16 PM Screenshot 2025-07-15 at 5 09 30 PM

Example usage (claude code)

Pre-requisites:

  • Run local fuel-core node
    fuel-core run --ip 127.0.0.1 --port 4000 --db-type rocks-db --debug --historical-execution
    
  • Deploy a contract to local node: forc deploy --default-signer
  1. Run the MCP server (using http-steam in this example):
cargo run -p forc-mcp -- http
  1. Add mcp server to claude
claude mcp add --transport http forc-mcp-http http://localhost:3001/mcp
  1. Use claude/LLM to interact with MCP server
Claude output
╭───────────────────────────────────────────────────╮
│ ✻ Welcome to Claude Code!                         │
│                                                   │
│   /help for help, /status for your current setup  │
│                                                   │
│   cwd: /Users/z/Desktop/fuellabs/sway             │
╰───────────────────────────────────────────────────╯


> Use the forc-mcp-http.

  Given the following sway contract:
  demo/demo/src/main.sw

  deployed at the following address:
  ae40e8dbe366c5ac61fe558e4e9139300534cd5d9463522621a903464c441eb2

  Using the forc-mcp-http:

  > list all its callable functions.
  > can you get the current count?
  > tell me how much gas it would cost to increment the count.
  > show me the execution trace for incrementing the count.
  ⎿  ⧉ Selected 14 lines from scratch/ai/templates.md in Cursor

⏺ I'll help you interact with the deployed Sway contract using forc-mcp-http. Let me start by reading
   the contract to understand its structure, then perform the requested operations.

⏺ Read(demo/demo/src/main.sw)
  ⎿  Read 34 lines (ctrl+r to expand)

⏺ Now I need to find the ABI file for this contract to use with the forc-mcp-http tools:

⏺ Search(pattern: "demo/demo/out/**/*.json")
  ⎿  Found 7 files (ctrl+r to expand)

⏺ Perfect! I found the ABI file and a deployment file that matches the contract address. Let me read
  the ABI:

⏺ Read(demo/demo/out/release/demo-abi.json)
  ⎿  Read 89 lines (ctrl+r to expand)

⏺ Now let me use the forc-mcp-http tools to interact with the contract. First, I'll list all callable
   functions:

⏺ forc-mcp-http:list_contract_functions (MCP)(abi: "/demo/demo/out/rele
                                             ase/demo-abi.json", contract_id: "ae40e8dbe366c5ac61fe55
                                             8e4e9139300534cd5d9463522621a903464c441eb2")
  ⎿ Callable functions for contract: ae40e8dbe366c5ac61fe558e4e9139300534cd5d9463522621a903464c441eb2

    get_count() -> u64
    … +186 lines (ctrl+r to expand)


⏺ Now let me get the current count:

⏺ forc-mcp-http:call_contract (MCP)(abi: "/demo/demo/out/release/demo-a
                                   bi.json", contract_id: "ae40e8dbe366c5ac61fe558e4e9139300534cd5d94
                                   63522621a903464c441eb2", function: "get_count")
  ⎿  {                                                                               
       "tx_hash": "0f1e868e75530f33d37748cac9ae25c33bf166c9e02d28e36f3ffdd67ecf3584",
       "total_gas": 65007,
     … +354 lines (ctrl+r to expand)

⏺ Now let me check the gas cost for incrementing the count using dry-run mode:

⏺ forc-mcp-http:call_contract (MCP)(abi: "/demo/demo/out/release/demo-a
                                   bi.json", contract_id: "ae40e8dbe366c5ac61fe558e4e9139300534cd5d94
                                   63522621a903464c441eb2", function: "increment_count",
                                   function_args: ["(1, 2)"], mode: "dry-run")
  ⎿  Error: Contract call failed: input must start with '{' and end with '}': (1, 2)

⏺ Let me fix the format for the struct argument:

⏺ forc-mcp-http:call_contract (MCP)(abi: "/demo/demo/out/release/demo-a
                                   bi.json", contract_id: "ae40e8dbe366c5ac61fe558e4e9139300534cd5d94
                                   63522621a903464c441eb2", function: "increment_count",
                                   function_args: ["{\"vals\": [1, 2]}"], mode: "dry-run")
  ⎿  Error: Contract call failed: input must start with '(' and end with ')': "vals": [1, 2]

⏺ Let me try the tuple format as shown in the example:

⏺ forc-mcp-http:call_contract (MCP)(abi: "/demo/demo/out/release/demo-a
                                   bi.json", contract_id: "ae40e8dbe366c5ac61fe558e4e9139300534cd5d94
                                   63522621a903464c441eb2", function: "increment_count",
                                   function_args: ["((1, 2))"], mode: "dry-run")
  ⎿  Error: Contract call failed: input must start with '{' and end with '}': ((1, 2))

⏺ Let me try with the correct struct format:

⏺ forc-mcp-http:call_contract (MCP)(abi: "/demo/demo/out/release/demo-a
                                   bi.json", contract_id: "ae40e8dbe366c5ac61fe558e4e9139300534cd5d94
                                   63522621a903464c441eb2", function: "increment_count",
                                   function_args: ["{(1, 2)}"], mode: "dry-run")
  ⎿  {                                                                               
       "tx_hash": "f694c73230e893b00968d8106f5bafbcd585c2cf0f4aea1c6a562ce7e1868690",
       "total_gas": 78940,
     … +399 lines (ctrl+r to expand)

⏺ Perfect! Now let me get the execution trace for that increment operation:

⏺ forc-mcp-http:get_execution_trace (MCP)(total_gas: 78940, trace_events: [{"Call":{"index":0,"method
                                         ":"increment_count","arguments":["Adder { vals: (1, 2)
                                         }"],"to":"ae40e8dbe366c5ac61fe558e4e9139300534cd5d9463522621
                                         a903464c441eb2","amount":0,"gas":26222}},{"LogData":{"index"
                                         :2,"id":"ae40e8dbe366c5ac61fe558e4e9139300534cd5d9463522621a
                                         903464c441eb2","value":"3","len":8}},{"ReturnData":{"index":
                                         4,"id":"ae40e8dbe366c5ac61fe558e4e9139300534cd5d9463522621a9
                                         03464c441eb2","data":"()"}},{"Return":{"index":6,"id":"00000
                                         00000000000000000000000000000000000000000000000000000000000"
                                         ,"val":1}},{"ScriptResult":{"index":7,"result":"Success","ga
                                         s_used":22210}}])
  ⎿  Traces:                                                                            
       [Script]
         ├─ [26222] 0xae40e8dbe366c5ac61fe558e4e9139300534cd5d9463522621a903464c441eb2::
     … +8 lines (ctrl+r to expand)

⏺ Summary

  Here are the results for the deployed Sway contract at
  ae40e8dbe366c5ac61fe558e4e9139300534cd5d9463522621a903464c441eb2:

  Callable Functions:

  1. get_count() → returns u64 (read-only)
  3. increment_count(adder: Adder) → returns () (read/write storage)

  Current Count:

  The current count is 1

  Gas Cost for Increment:

  Incrementing the count with Adder { vals: (1, 2) } costs 78,940 gas total (22,210 gas used by
  script)

  Execution Trace:

  The increment operation:
  - Calls increment_count with Adder { vals: (1, 2) }
  - Uses 26,222 gas for the contract call
  - Emits a log with value 3 (0 + 1 + 2 = 3, the new count)
  - Returns () (unit/void)
  - Total script gas used: 22,210
  - Result: Success

  The contract adds the two values from the Adder struct (1 + 2 = 3) to the current storage count (0)
   and logs the result.

Checklist

  • I have linked to any relevant issues.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have updated the documentation where relevant (API docs, the reference, and the Sway book).
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added (or requested a maintainer to add) the necessary Breaking* or New Feature labels where relevant.
  • I have done my best to ensure that my PR adheres to the Fuel Labs Code Review Standards.
  • I have requested a review from the relevant team or maintainers.

@zees-dev zees-dev requested review from a team as code owners July 15, 2025 05:23
@zees-dev zees-dev self-assigned this Jul 15, 2025
@zees-dev zees-dev added enhancement New feature or request forc documentation dev-experience Anything to do with the developer experience forc-call Everything related to the `forc-call` plugin in `forc-client` package team:tooling Tooling Team forc-mcp Everything to do with forc mcp server labels Jul 15, 2025
@PraneshASP
Copy link

Great stuff! Are we supposed to run the server inside a sway project to get access to the contract ABIs? If so, it would be great if we can also enable access to

1.) Verified assets - so that users can easily send transactions just by posting asset names rather than ids.
2.) Ecosystem projects - to enable the agent to fetch the ABI with the project name (swaylend, mira, etc.,)

You can find a reference implementation here

@zees-dev

@zees-dev
Copy link
Contributor Author

Great stuff! Are we supposed to run the server inside a sway project to get access to the contract ABIs? If so, it would be great if we can also enable access to

1.) Verified assets - so that users can easily send transactions just by posting asset names rather than ids. 2.) Ecosystem projects - to enable the agent to fetch the ABI with the project name (swaylend, mira, etc.,)

You can find a reference implementation here

@zees-dev

The abi can be provided inline or as URL; hence does not need to be in the server.

  1. Good ideas here; however there is a transfer function just for this scenario.
    In fact, we can probably provide verified assets (or a link to them) as a re-source, the LLM/agent could fetch these addresses and provide them as part of the transfer request.

  2. Also a good idea; however not sure if we want to hardcode ecosystem projects within the codebase. Maybe a user could provide a link to them. Additionally we now have forc.pub - which should be the canonical registry where ecosystem projects should/will be located. A future PR could introduce an additional tool-call to fetch from registry (once registry supports ABI retrieval).

In any case all good ideas here - this PR will simply introduce building blocks for performing low level operations (calls, transfers); future PRs could add additional tool-calls/resources (such as ABI generation from sway contract, compilation of contract, abi retrieval from registry, etc.) which should further enhance the functionality.

kayagokalp
kayagokalp previously approved these changes Jul 24, 2025
Copy link
Member

@kayagokalp kayagokalp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

@zees-dev zees-dev enabled auto-merge (squash) July 24, 2025 13:31
@zees-dev zees-dev merged commit 4c9c4c2 into master Jul 24, 2025
44 checks passed
@zees-dev zees-dev deleted the feat/forc-mcp branch July 24, 2025 15:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dev-experience Anything to do with the developer experience documentation enhancement New feature or request forc forc-call Everything related to the `forc-call` plugin in `forc-client` package forc-mcp Everything to do with forc mcp server team:tooling Tooling Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants