Skip to main content
Glama
thomasfevre

LayerZero OFT MCP Server

by thomasfevre

deploy-and-configure-oft-multichain

Deploy and configure Omnichain Fungible Tokens (OFTs) across multiple blockchains, set up peer connections, and enforce token options using LayerZero protocols.

Instructions

Deploys an OFT contract to multiple chains, sets up peer connections, and configures enforced options.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
decimalsNoNumber of decimals for the token (default: 18)
initialTotalSupplyYesTotal supply of the token in human-readable format (e.g., '1000000')
ownerNoOptional owner address. Defaults to OWNER_ADDRESS from .env if not provided.
targetChainsYesList of chain names to deploy and configure the OFT on (e.g., ['ArbitrumSepolia', 'baseSepolia'])
tokenNameYesName of the token (e.g., MyToken)
tokenSymbolYesSymbol of the token (e.g., MYT)

Implementation Reference

  • The main handler function that performs multi-chain deployment of OFT contracts via CREATE2 factories, bidirectional peering setup using setPeer, and enforced options configuration using setEnforcedOptions for LayerZero cross-chain functionality.
    async (params: z.infer<typeof deployAndConfigureOftParams>, _extra) => {
      const results: string[] = [];
      const deployedContractsSummary: Array<{
        chainName: string;
        contractAddress: string | null;
        deploymentStatus: string;
        error?: string;
      }> = [];
    
      // A. Initial Checks & Setup
      results.push("Phase A: Initial Checks & Setup started.");
      const envOwnerAddress = process.env.OWNER_ADDRESS;
      if (!envOwnerAddress) {
        results.push("Error: OWNER_ADDRESS is not set in environment variables.");
        return {
          content: [
            {
              type: "text",
              text: `Error: OWNER_ADDRESS is not set in environment variables.`,
            },
          ],
          isError: true,
        };
      }
      results.push(`OWNER_ADDRESS found: ${envOwnerAddress}`);
    
      const MyOFT = JSON.parse(await readFile(oftPath, "utf8"));
      const OFT_ABI = MyOFT.abi;
      let OFT_BYTECODE = MyOFT.bytecode.object;
    
      if (
        OFT_ABI === "ABI_PLACEHOLDER" ||
        OFT_BYTECODE === "BYTECODE_PLACEHOLDER" ||
        OFT_ABI === undefined ||
        OFT_BYTECODE === undefined ||
        OFT_ABI.length === 0 ||
        OFT_BYTECODE.length === 0
      ) {
        results.push(
          "Error: OFT_ABI or OFT_BYTECODE are placeholders. Please replace them in layerzero-mcp.ts."
        );
        return {
          content: [
            {
              type: "text",
              text: `Error: OFT_ABI or OFT_BYTECODE are placeholders. Please replace them in layerzero-mcp.ts.`,
            },
          ],
          isError: true,
        };
      }
      results.push("OFT_ABI and OFT_BYTECODE are not placeholders.");
    
      if (OFT_BYTECODE && !OFT_BYTECODE.startsWith('0x')) {
        OFT_BYTECODE = '0x' + OFT_BYTECODE;
      }
    
      if (!params.targetChains || params.targetChains.length === 0) {
        // Zod schema min(1) should prevent this, but good to double check
        results.push("Error: targetChains array is empty.");
        return {
          content: [
            {
              type: "text",
              text: `Error: Failed to Deploy OFT: Error: targetChains array is empty. Please provide at least one target chain.`,
            },
          ],
          isError: true,
        };
      }
      results.push(
        `Target chains for deployment: ${params.targetChains.join(", ")}`
      );
    
      interface DeployedContractData {
        chainName: string;
        address: string;
        contractInstance: ethers.Contract;
        lzEid: number;
        networkConfig: NetworkConfig;
      }
      const deployedContractsData: DeployedContractData[] = [];
      results.push("Initialization complete.");
    
      // B. Deployment Phase
      results.push("\nPhase B: Deployment Phase started.");
      const deployOwner = params.owner || envOwnerAddress;
      results.push(`Deployment owner set to: ${deployOwner}`);
    
      for (const chainName of params.targetChains) {
        results.push(`Attempting deployment on ${chainName}...`);
        try {
          const signer = await getSigner(chainName);
          const networkConfig = getNetworkConfig(chainName);
          results.push(
            `  Signer and network config obtained for ${chainName}. RPC: ${networkConfig.rpc}, LZ EID: ${networkConfig.lzEid}`
          );
    
          const parsedSupply = ethers.parseUnits(params.initialTotalSupply, 0);
          results.push(
            `  Token: ${params.tokenName} (${params.tokenSymbol}), Supply: ${
              params.initialTotalSupply
            } (parsed: ${parsedSupply.toString()}), Decimals: ${params.decimals}`
          );
    
          const factoryAddress = FACTORY_ADDRESSES[chainName];
          if (!factoryAddress) {
            throw new Error(`Factory address not defined for ${chainName}`);
          }
          const factoryJSON = JSON.parse(await readFile(factoryPath, "utf8"));
          const factory = new Contract(factoryAddress, factoryJSON.abi, signer);
          results.push("  Contract factory created.");
    
          // Derive a consistent salt from token name and chain (e.g., to keep same address across chains)
          const salt = ethers.keccak256(
            ethers.toUtf8Bytes(`${params.tokenName}:${params.tokenSymbol}`)
          );
    
          // Encode constructor args for MyOFT
          const constructorArgs = new ethers.Interface(OFT_ABI).encodeDeploy([
            params.tokenName,
            params.tokenSymbol,
            parsedSupply,
            networkConfig.lzEndpoint,
            deployOwner,
          ]);
    
          // Combine bytecode + constructor args
          const bytecodeWithArgs = ethers.solidityPacked(
            ["bytes", "bytes"],
            [OFT_BYTECODE, constructorArgs]
          );
    
          results.push(
            `  Deploying with CREATE2 on ${chainName} using salt: ${salt}`
          );
          const tx = await factory.deploy(bytecodeWithArgs, salt);
          await tx.wait();
    
          const computedAddress = await factory.lastDeployedAddress();
          results.push(
            `  SUCCESS: Deployed via CREATE2 at computed address: ${computedAddress}`
          );
    
          const contractInstance = new ethers.Contract(
            computedAddress,
            OFT_ABI,
            signer
          );
          deployedContractsData.push({
            chainName,
            address: computedAddress,
            contractInstance,
            lzEid: networkConfig.lzEid,
            networkConfig,
          });
          deployedContractsSummary.push({
            chainName,
            contractAddress: computedAddress,
            deploymentStatus: "Success",
          });
        } catch (error: any) {
          const constructorArgs = new ethers.Interface(OFT_ABI).encodeDeploy([
            params.tokenName,
            params.tokenSymbol,
            ethers.parseUnits(params.initialTotalSupply, 0),
            getNetworkConfig(chainName).lzEndpoint,
            deployOwner,
          ]);
    
          // Combine bytecode + constructor args
          const bytecodeWithArgs = ethers.solidityPacked(
            ["bytes", "bytes"],
            [OFT_BYTECODE, constructorArgs]
          );
          const errorMessage = `  FAILURE: Deploying on ${chainName}: ${
            error.message || error.toString()
          }, salt: 
          ${ethers.keccak256(
            ethers.toUtf8Bytes(`${params.tokenName}:${params.tokenSymbol}`)
          )}, bytecode: ${bytecodeWithArgs}`;
          results.push(errorMessage);
          console.error(errorMessage, error);
          deployedContractsSummary.push({
            chainName,
            contractAddress: null,
            deploymentStatus: "Failed",
            error: error.message || error.toString(),
          });
          return {
            content: [
              {
                type: "text",
                text: `Error: Failed to Deploy OFT: ${errorMessage}`,
              },
            ],
            isError: true,
          };
        }
      }
      results.push("Deployment Phase completed.");
    
      // C. Peering Phase
      results.push("\nPhase C: Peering Phase started.");
      const peeringResults: string[] = [];
      if (deployedContractsData.length < 2) {
        const msg =
          "Skipping peering phase: Less than 2 contracts successfully deployed.";
        results.push(msg);
        peeringResults.push(msg);
      } else {
        results.push(
          `Found ${deployedContractsData.length} successfully deployed contracts for peering.`
        );
        for (const contractAData of deployedContractsData) {
          for (const contractBData of deployedContractsData) {
            if (contractAData.chainName === contractBData.chainName) continue;
    
            const logPrefix = `  Peering ${contractAData.chainName} (EID: ${contractAData.lzEid}) with ${contractBData.chainName} (EID: ${contractBData.lzEid}, Address: ${contractBData.address}):`;
            try {
              const peerAddressBytes32 = formatAddressForLayerZero(
                contractBData.address
              );
              peeringResults.push(
                `${logPrefix} Formatting peer address ${contractBData.address} to ${peerAddressBytes32}`
              );
    
              const tx = await contractAData.contractInstance.setPeer(
                contractBData.lzEid,
                peerAddressBytes32
              );
              peeringResults.push(
                `${logPrefix} setPeer transaction sent. Waiting for confirmation... TxHash: ${tx.hash}`
              );
              await tx.wait();
              const successMsg = `${logPrefix} SUCCESS.`;
              results.push(successMsg);
              peeringResults.push(successMsg);
            } catch (error: any) {
              const errorMsg = `${logPrefix} FAILURE: ${
                error.message || error.toString()
              }`;
              results.push(errorMsg);
              peeringResults.push(errorMsg);
              console.error(errorMsg, error);
              return {
                content: [
                  {
                    type: "text",
                    text: `Error: Failed to Deploy OFT: ${errorMsg}`,
                  },
                ],
                isError: true,
              };
            }
          }
        }
      }
      results.push("Peering Phase completed.");
    
      // D. Enforced Options Phase
      results.push("\nPhase D: Enforced Options Phase started.");
      const enforcedOptionsResults: string[] = [];
      const standardOptionsHex = "0x00030100110100000000000000000000000000030d40"; // 200k gas limit
      results.push(
        `Standard options hex for enforced options: ${standardOptionsHex}`
      );
    
      if (deployedContractsData.length === 0) {
        const msg =
          "Skipping enforced options phase: No contracts successfully deployed.";
        results.push(msg);
        enforcedOptionsResults.push(msg);
      } else {
        for (const contractData of deployedContractsData) {
          const logPrefix = `  Configuring enforced options on ${contractData.chainName} (Address: ${contractData.address}):`;
          try {
            const optionsToSet: Array<{
              eid: number;
              msgType: number;
              options: string;
            }> = [];
            for (const peerData of deployedContractsData) {
              if (contractData.chainName === peerData.chainName) continue;
              optionsToSet.push({
                eid: peerData.lzEid,
                msgType: 1,
                options: standardOptionsHex,
              });
            }
    
            if (optionsToSet.length > 0) {
              enforcedOptionsResults.push(
                `${logPrefix} Preparing to set options for ${
                  optionsToSet.length
                } peers: ${optionsToSet.map((o) => `EID ${o.eid}`).join(", ")}`
              );
              const tx = await contractData.contractInstance.setEnforcedOptions(
                optionsToSet
              );
              enforcedOptionsResults.push(
                `${logPrefix} setEnforcedOptions transaction sent. Waiting for confirmation... TxHash: ${tx.hash}`
              );
              await tx.wait();
              const successMsg = `${logPrefix} SUCCESS: Set enforced options for ${optionsToSet.length} peers.`;
              results.push(successMsg);
              enforcedOptionsResults.push(successMsg);
            } else {
              const noPeersMsg = `${logPrefix} No peers to set enforced options for.`;
              results.push(noPeersMsg);
              enforcedOptionsResults.push(noPeersMsg);
            }
          } catch (error: any) {
            const errorMsg = `${logPrefix} FAILURE: ${
              error.message || error.toString()
            }`;
            results.push(errorMsg);
            enforcedOptionsResults.push(errorMsg);
            console.error(errorMsg, error);
            return {
              content: [
                {
                  type: "text",
                  text: `Error: Failed to Deploy OFT: ${errorMsg}`,
                },
              ],
              isError: true,
            };
          }
        }
      }
      results.push("Enforced Options Phase completed.");
    
      // E. Return Value
      results.push("\nPhase E: Preparing results.");
      let overallStatus = "Deployment and configuration process completed.";
      if (
        deployedContractsSummary.some((s) => s.deploymentStatus === "Failed") ||
        results.some((r) => r.includes("FAILURE"))
      ) {
        overallStatus += " Some errors occurred. Check detailed logs.";
      } else if (deployedContractsData.length === 0) {
        overallStatus = "Deployment failed on all target chains.";
      } else {
        overallStatus =
          "Successfully deployed and configured on all attempted chains where deployment succeeded.";
      }
      results.push(`Overall Status: ${overallStatus}`);
    
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(
              {
                overallStatus,
                deployedContracts: deployedContractsSummary,
                peeringResults,
                enforcedOptionsResults,
                detailedExecutionLog: results,
              },
              null,
              2
            ), // Pretty print JSON
          },
        ],
      };
    }
  • Zod schema defining the input parameters for the deploy-and-configure-oft-multichain tool.
    const deployAndConfigureOftParams = z.object({
      tokenName: z.string().describe("Name of the token (e.g., MyToken)"),
      tokenSymbol: z.string().describe("Symbol of the token (e.g., MYT)"),
      initialTotalSupply: z
        .string()
        .describe(
          "Total supply of the token in human-readable format (e.g., '1000000')"
        ),
      decimals: z
        .number()
        .int()
        .min(0)
        .max(18)
        .optional()
        .default(18)
        .describe("Number of decimals for the token (default: 18)"),
      targetChains: z
        .array(z.enum(networkKeysForEnum))
        .min(1)
        .describe(
          "List of chain names to deploy and configure the OFT on (e.g., ['ArbitrumSepolia', 'baseSepolia'])"
        ),
      owner: z
        .string()
        .optional()
        .describe(
          "Optional owner address. Defaults to OWNER_ADDRESS from .env if not provided."
        ),
    });
  • src/index.ts:97-469 (registration)
    MCP server tool registration call that associates the tool name, description, input schema, and handler function.
    server.tool(
      "deploy-and-configure-oft-multichain",
      "Deploys an OFT contract to multiple chains, sets up peer connections, and configures enforced options.",
      deployAndConfigureOftParams.shape,
      async (params: z.infer<typeof deployAndConfigureOftParams>, _extra) => {
        const results: string[] = [];
        const deployedContractsSummary: Array<{
          chainName: string;
          contractAddress: string | null;
          deploymentStatus: string;
          error?: string;
        }> = [];
    
        // A. Initial Checks & Setup
        results.push("Phase A: Initial Checks & Setup started.");
        const envOwnerAddress = process.env.OWNER_ADDRESS;
        if (!envOwnerAddress) {
          results.push("Error: OWNER_ADDRESS is not set in environment variables.");
          return {
            content: [
              {
                type: "text",
                text: `Error: OWNER_ADDRESS is not set in environment variables.`,
              },
            ],
            isError: true,
          };
        }
        results.push(`OWNER_ADDRESS found: ${envOwnerAddress}`);
    
        const MyOFT = JSON.parse(await readFile(oftPath, "utf8"));
        const OFT_ABI = MyOFT.abi;
        let OFT_BYTECODE = MyOFT.bytecode.object;
    
        if (
          OFT_ABI === "ABI_PLACEHOLDER" ||
          OFT_BYTECODE === "BYTECODE_PLACEHOLDER" ||
          OFT_ABI === undefined ||
          OFT_BYTECODE === undefined ||
          OFT_ABI.length === 0 ||
          OFT_BYTECODE.length === 0
        ) {
          results.push(
            "Error: OFT_ABI or OFT_BYTECODE are placeholders. Please replace them in layerzero-mcp.ts."
          );
          return {
            content: [
              {
                type: "text",
                text: `Error: OFT_ABI or OFT_BYTECODE are placeholders. Please replace them in layerzero-mcp.ts.`,
              },
            ],
            isError: true,
          };
        }
        results.push("OFT_ABI and OFT_BYTECODE are not placeholders.");
    
        if (OFT_BYTECODE && !OFT_BYTECODE.startsWith('0x')) {
          OFT_BYTECODE = '0x' + OFT_BYTECODE;
        }
    
        if (!params.targetChains || params.targetChains.length === 0) {
          // Zod schema min(1) should prevent this, but good to double check
          results.push("Error: targetChains array is empty.");
          return {
            content: [
              {
                type: "text",
                text: `Error: Failed to Deploy OFT: Error: targetChains array is empty. Please provide at least one target chain.`,
              },
            ],
            isError: true,
          };
        }
        results.push(
          `Target chains for deployment: ${params.targetChains.join(", ")}`
        );
    
        interface DeployedContractData {
          chainName: string;
          address: string;
          contractInstance: ethers.Contract;
          lzEid: number;
          networkConfig: NetworkConfig;
        }
        const deployedContractsData: DeployedContractData[] = [];
        results.push("Initialization complete.");
    
        // B. Deployment Phase
        results.push("\nPhase B: Deployment Phase started.");
        const deployOwner = params.owner || envOwnerAddress;
        results.push(`Deployment owner set to: ${deployOwner}`);
    
        for (const chainName of params.targetChains) {
          results.push(`Attempting deployment on ${chainName}...`);
          try {
            const signer = await getSigner(chainName);
            const networkConfig = getNetworkConfig(chainName);
            results.push(
              `  Signer and network config obtained for ${chainName}. RPC: ${networkConfig.rpc}, LZ EID: ${networkConfig.lzEid}`
            );
    
            const parsedSupply = ethers.parseUnits(params.initialTotalSupply, 0);
            results.push(
              `  Token: ${params.tokenName} (${params.tokenSymbol}), Supply: ${
                params.initialTotalSupply
              } (parsed: ${parsedSupply.toString()}), Decimals: ${params.decimals}`
            );
    
            const factoryAddress = FACTORY_ADDRESSES[chainName];
            if (!factoryAddress) {
              throw new Error(`Factory address not defined for ${chainName}`);
            }
            const factoryJSON = JSON.parse(await readFile(factoryPath, "utf8"));
            const factory = new Contract(factoryAddress, factoryJSON.abi, signer);
            results.push("  Contract factory created.");
    
            // Derive a consistent salt from token name and chain (e.g., to keep same address across chains)
            const salt = ethers.keccak256(
              ethers.toUtf8Bytes(`${params.tokenName}:${params.tokenSymbol}`)
            );
    
            // Encode constructor args for MyOFT
            const constructorArgs = new ethers.Interface(OFT_ABI).encodeDeploy([
              params.tokenName,
              params.tokenSymbol,
              parsedSupply,
              networkConfig.lzEndpoint,
              deployOwner,
            ]);
    
            // Combine bytecode + constructor args
            const bytecodeWithArgs = ethers.solidityPacked(
              ["bytes", "bytes"],
              [OFT_BYTECODE, constructorArgs]
            );
    
            results.push(
              `  Deploying with CREATE2 on ${chainName} using salt: ${salt}`
            );
            const tx = await factory.deploy(bytecodeWithArgs, salt);
            await tx.wait();
    
            const computedAddress = await factory.lastDeployedAddress();
            results.push(
              `  SUCCESS: Deployed via CREATE2 at computed address: ${computedAddress}`
            );
    
            const contractInstance = new ethers.Contract(
              computedAddress,
              OFT_ABI,
              signer
            );
            deployedContractsData.push({
              chainName,
              address: computedAddress,
              contractInstance,
              lzEid: networkConfig.lzEid,
              networkConfig,
            });
            deployedContractsSummary.push({
              chainName,
              contractAddress: computedAddress,
              deploymentStatus: "Success",
            });
          } catch (error: any) {
            const constructorArgs = new ethers.Interface(OFT_ABI).encodeDeploy([
              params.tokenName,
              params.tokenSymbol,
              ethers.parseUnits(params.initialTotalSupply, 0),
              getNetworkConfig(chainName).lzEndpoint,
              deployOwner,
            ]);
    
            // Combine bytecode + constructor args
            const bytecodeWithArgs = ethers.solidityPacked(
              ["bytes", "bytes"],
              [OFT_BYTECODE, constructorArgs]
            );
            const errorMessage = `  FAILURE: Deploying on ${chainName}: ${
              error.message || error.toString()
            }, salt: 
            ${ethers.keccak256(
              ethers.toUtf8Bytes(`${params.tokenName}:${params.tokenSymbol}`)
            )}, bytecode: ${bytecodeWithArgs}`;
            results.push(errorMessage);
            console.error(errorMessage, error);
            deployedContractsSummary.push({
              chainName,
              contractAddress: null,
              deploymentStatus: "Failed",
              error: error.message || error.toString(),
            });
            return {
              content: [
                {
                  type: "text",
                  text: `Error: Failed to Deploy OFT: ${errorMessage}`,
                },
              ],
              isError: true,
            };
          }
        }
        results.push("Deployment Phase completed.");
    
        // C. Peering Phase
        results.push("\nPhase C: Peering Phase started.");
        const peeringResults: string[] = [];
        if (deployedContractsData.length < 2) {
          const msg =
            "Skipping peering phase: Less than 2 contracts successfully deployed.";
          results.push(msg);
          peeringResults.push(msg);
        } else {
          results.push(
            `Found ${deployedContractsData.length} successfully deployed contracts for peering.`
          );
          for (const contractAData of deployedContractsData) {
            for (const contractBData of deployedContractsData) {
              if (contractAData.chainName === contractBData.chainName) continue;
    
              const logPrefix = `  Peering ${contractAData.chainName} (EID: ${contractAData.lzEid}) with ${contractBData.chainName} (EID: ${contractBData.lzEid}, Address: ${contractBData.address}):`;
              try {
                const peerAddressBytes32 = formatAddressForLayerZero(
                  contractBData.address
                );
                peeringResults.push(
                  `${logPrefix} Formatting peer address ${contractBData.address} to ${peerAddressBytes32}`
                );
    
                const tx = await contractAData.contractInstance.setPeer(
                  contractBData.lzEid,
                  peerAddressBytes32
                );
                peeringResults.push(
                  `${logPrefix} setPeer transaction sent. Waiting for confirmation... TxHash: ${tx.hash}`
                );
                await tx.wait();
                const successMsg = `${logPrefix} SUCCESS.`;
                results.push(successMsg);
                peeringResults.push(successMsg);
              } catch (error: any) {
                const errorMsg = `${logPrefix} FAILURE: ${
                  error.message || error.toString()
                }`;
                results.push(errorMsg);
                peeringResults.push(errorMsg);
                console.error(errorMsg, error);
                return {
                  content: [
                    {
                      type: "text",
                      text: `Error: Failed to Deploy OFT: ${errorMsg}`,
                    },
                  ],
                  isError: true,
                };
              }
            }
          }
        }
        results.push("Peering Phase completed.");
    
        // D. Enforced Options Phase
        results.push("\nPhase D: Enforced Options Phase started.");
        const enforcedOptionsResults: string[] = [];
        const standardOptionsHex = "0x00030100110100000000000000000000000000030d40"; // 200k gas limit
        results.push(
          `Standard options hex for enforced options: ${standardOptionsHex}`
        );
    
        if (deployedContractsData.length === 0) {
          const msg =
            "Skipping enforced options phase: No contracts successfully deployed.";
          results.push(msg);
          enforcedOptionsResults.push(msg);
        } else {
          for (const contractData of deployedContractsData) {
            const logPrefix = `  Configuring enforced options on ${contractData.chainName} (Address: ${contractData.address}):`;
            try {
              const optionsToSet: Array<{
                eid: number;
                msgType: number;
                options: string;
              }> = [];
              for (const peerData of deployedContractsData) {
                if (contractData.chainName === peerData.chainName) continue;
                optionsToSet.push({
                  eid: peerData.lzEid,
                  msgType: 1,
                  options: standardOptionsHex,
                });
              }
    
              if (optionsToSet.length > 0) {
                enforcedOptionsResults.push(
                  `${logPrefix} Preparing to set options for ${
                    optionsToSet.length
                  } peers: ${optionsToSet.map((o) => `EID ${o.eid}`).join(", ")}`
                );
                const tx = await contractData.contractInstance.setEnforcedOptions(
                  optionsToSet
                );
                enforcedOptionsResults.push(
                  `${logPrefix} setEnforcedOptions transaction sent. Waiting for confirmation... TxHash: ${tx.hash}`
                );
                await tx.wait();
                const successMsg = `${logPrefix} SUCCESS: Set enforced options for ${optionsToSet.length} peers.`;
                results.push(successMsg);
                enforcedOptionsResults.push(successMsg);
              } else {
                const noPeersMsg = `${logPrefix} No peers to set enforced options for.`;
                results.push(noPeersMsg);
                enforcedOptionsResults.push(noPeersMsg);
              }
            } catch (error: any) {
              const errorMsg = `${logPrefix} FAILURE: ${
                error.message || error.toString()
              }`;
              results.push(errorMsg);
              enforcedOptionsResults.push(errorMsg);
              console.error(errorMsg, error);
              return {
                content: [
                  {
                    type: "text",
                    text: `Error: Failed to Deploy OFT: ${errorMsg}`,
                  },
                ],
                isError: true,
              };
            }
          }
        }
        results.push("Enforced Options Phase completed.");
    
        // E. Return Value
        results.push("\nPhase E: Preparing results.");
        let overallStatus = "Deployment and configuration process completed.";
        if (
          deployedContractsSummary.some((s) => s.deploymentStatus === "Failed") ||
          results.some((r) => r.includes("FAILURE"))
        ) {
          overallStatus += " Some errors occurred. Check detailed logs.";
        } else if (deployedContractsData.length === 0) {
          overallStatus = "Deployment failed on all target chains.";
        } else {
          overallStatus =
            "Successfully deployed and configured on all attempted chains where deployment succeeded.";
        }
        results.push(`Overall Status: ${overallStatus}`);
    
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(
                {
                  overallStatus,
                  deployedContracts: deployedContractsSummary,
                  peeringResults,
                  enforcedOptionsResults,
                  detailedExecutionLog: results,
                },
                null,
                2
              ), // Pretty print JSON
            },
          ],
        };
      }
    );
  • Helper function to create an ethers Wallet signer connected to the provider for the specified network, used in deployment and configuration transactions.
    export async function getSigner(networkName: string): Promise<ethers.Wallet> {
        const networkConfig = getNetworkConfig(networkName);
        const provider = new JsonRpcProvider(networkConfig.rpc);
        const wallet = new Wallet(PRIVATE_KEY!, provider);
        return wallet;
    }
  • Helper function to format Ethereum addresses to 32-byte padded strings required for LayerZero peer addresses.
    export function formatAddressForLayerZero(address: string): string {
        return zeroPadValue(address, 32);
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden but only mentions high-level actions without disclosing critical behavioral traits like permission requirements, cost implications, whether deployment is reversible, rate limits, or error handling. It states what the tool does but not how it behaves.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Single sentence efficiently captures the core functionality with zero wasted words. It's appropriately sized and front-loaded, making every word earn its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex deployment tool with 6 parameters, no annotations, and no output schema, the description is insufficient. It lacks information about what happens after deployment, error conditions, side effects, or integration context, leaving significant gaps for an AI agent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, providing detailed parameter documentation. The description adds no additional parameter semantics beyond what's in the schema, so it meets the baseline of 3 without compensating for any gaps.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('deploys an OFT contract to multiple chains, sets up peer connections, and configures enforced options') with the resource (OFT contract) and distinguishes it from the sibling tool 'bridge-oft' by focusing on deployment and configuration rather than bridging operations.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No explicit guidance on when to use this tool versus alternatives like 'bridge-oft' or other deployment methods. The description implies usage for initial setup but lacks context on prerequisites, timing, or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/thomasfevre/layerzero_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server