Skip to main content
Glama

getNFTInfo

Retrieve ERC-721 NFT holdings and tokenURIs for a wallet address from a specific smart contract on EVM chains like Ethereum, Polygon, and Arbitrum.

Instructions

특정 컨트랙트에서 지갑 주소가 보유한 ERC-721 NFT 목록과 tokenURI를 조회합니다

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
addressYes조회할 지갑 주소 (0x...)
chainNoEVM 체인ethereum
contractAddressNo특정 NFT 컨트랙트 주소 (미지정 시 오류 반환)

Implementation Reference

  • The handler function implements the logic for fetching NFT information by checking balance, token IDs via enumeration, and tokenURIs via RPC.
    async function handler(args: z.infer<typeof inputSchema>): Promise<ToolResult<NFTInfoData>> {
      const { address, chain, contractAddress } = args;
    
      // contractAddress 없이는 인덱서 없이 NFT 열거 불가
      if (!contractAddress) {
        return makeError(
          "contractAddress is required. Enumerating all NFTs without an indexer is not supported.",
          "INVALID_INPUT",
        );
      }
    
      const cacheKey = `nftinfo:${chain}:${contractAddress.toLowerCase()}:${address.toLowerCase()}`;
      const cached = cache.get<NFTInfoData>(cacheKey);
      if (cached.hit) return makeSuccess(chain, cached.data, true);
    
      try {
        const client = getClient(chain);
        const ownerAddr = address as `0x${string}`;
        const contractAddr = contractAddress as `0x${string}`;
    
        // 1단계: balanceOf로 보유 수량 확인
        const balance = await client.readContract({
          address: contractAddr,
          abi: ERC721_ABI,
          functionName: "balanceOf",
          args: [ownerAddr],
        }) as bigint;
    
        const tokenCount = Number(balance);
    
        // 보유 토큰 없으면 빈 배열 반환
        if (tokenCount === 0) {
          const data: NFTInfoData = {
            owner: address,
            contractAddress,
            totalOwned: 0,
            tokens: [],
          };
          cache.set(cacheKey, data, NFT_INFO_CACHE_TTL);
          return makeSuccess(chain, data, false);
        }
    
        // 최대 MAX_TOKENS개까지만 조회
        const fetchCount = Math.min(tokenCount, MAX_TOKENS);
    
        // 2단계: tokenOfOwnerByIndex로 각 인덱스의 tokenId 조회
        const tokenIdResults = await Promise.allSettled(
          Array.from({ length: fetchCount }, (_, i) =>
            client.readContract({
              address: contractAddr,
              abi: ERC721_ABI,
              functionName: "tokenOfOwnerByIndex",
              args: [ownerAddr, BigInt(i)],
            }),
          ),
        );
    
        // 성공한 tokenId 목록 수집
        const tokenIds: bigint[] = [];
        for (const result of tokenIdResults) {
          if (result.status === "fulfilled") {
            tokenIds.push(result.value as bigint);
          }
        }
    
        // 3단계: 각 tokenId에 대해 tokenURI 조회
        const tokenURIResults = await Promise.allSettled(
          tokenIds.map((tokenId) =>
            client.readContract({
              address: contractAddr,
              abi: ERC721_ABI,
              functionName: "tokenURI",
              args: [tokenId],
            }),
          ),
        );
    
        // NFTItem 배열 구성
        const tokens: NFTItem[] = tokenIds.map((tokenId, i) => {
          const uriResult = tokenURIResults[i];
          return {
            tokenId: tokenId.toString(),
            tokenURI: uriResult.status === "fulfilled" ? (uriResult.value as string) : "",
            contractAddress,
          };
        });
    
        const data: NFTInfoData = {
          owner: address,
          contractAddress,
          totalOwned: tokenCount,
          tokens,
        };
    
        cache.set(cacheKey, data, NFT_INFO_CACHE_TTL);
        return makeSuccess(chain, data, false);
      } catch (err) {
        const message = sanitizeError(err);
        return makeError(`Failed to fetch NFT info: ${message}`, "RPC_ERROR");
      }
    }
  • Zod schema definition for validating the input arguments to the getNFTInfo tool.
    const inputSchema = z.object({
      address: z.string().describe("조회할 지갑 주소 (0x...)"),
      chain: z.enum(SUPPORTED_CHAINS).default("ethereum").describe("EVM 체인"),
      contractAddress: z.string().optional().describe("특정 NFT 컨트랙트 주소 (미지정 시 오류 반환)"),
    });
  • The register function adds the getNFTInfo tool to the MCP server.
    export function register(server: McpServer) {
      server.tool(
        "getNFTInfo",
        "특정 컨트랙트에서 지갑 주소가 보유한 ERC-721 NFT 목록과 tokenURI를 조회합니다",
        inputSchema.shape,
        async (args) => {
          const result = await handler(args as z.infer<typeof inputSchema>);
          return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
        },
      );
    }
Behavior3/5

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

No annotations provided, so description carries full behavioral disclosure burden. It specifies ERC-721 standard and mentions tokenURI (indicating part of return data), but lacks safety disclosures (read-only status), pagination behavior, rate limits, or error handling details. '조회합니다' (queries/retrieves) implies read-only but this is not explicit.

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, front-loaded with specific verb (조회합니다). Efficiently packs resource (ERC-721 NFT list, tokenURI), scope (specific contract), and target (wallet address) with zero redundancy. Appropriate length for the complexity.

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

Completeness4/5

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

With 3 parameters and 100% schema coverage but no output schema, the description appropriately mentions tokenURI to hint at return structure and specifies ERC-721 standard to constrain scope. Could benefit from describing the full return object format or pagination since no output schema exists, but adequately complete for tool selection.

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 has 100% description coverage. Description mentions '지갑 주소' (wallet address) and '특정 컨트랙트' (specific contract), matching schema fields, but adds no syntax details, format examples, or constraints beyond what the schema already documents. Baseline 3 applies for high schema coverage.

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?

Clear specific verb (조회합니다/retrieve) with explicit resource (ERC-721 NFT 목록/list of ERC-721 NFTs). Distinguishes from sibling getNFTMetadata (which implies individual metadata lookup) by specifying wallet-held asset listing scope and tokenURI retrieval. Precisely defines the operation's bounds.

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 when-to-use guidance, comparisons to siblings (e.g., when to use getPortfolio vs this), or prerequisites mentioned. The phrase '특정 컨트랙트에서' (from a specific contract) implies contractAddress necessity but does not constitute usage guidance versus alternatives.

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

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/calintzy/evmscope'

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