getNFTMetadata
Retrieve ERC-721 NFT metadata including name, description, image, and attributes from EVM chains. Supports automatic IPFS URI conversion for accessible data retrieval.
Instructions
ERC-721 NFT의 메타데이터(이름, 설명, 이미지, 속성)를 조회합니다. IPFS URI 자동 변환 지원
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| contractAddress | Yes | NFT 컨트랙트 주소 (0x...) | |
| tokenId | Yes | 조회할 토큰 ID | |
| chain | No | EVM 체인 | ethereum |
Implementation Reference
- src/tools/getNFTMetadata.ts:54-167 (handler)The 'handler' function executes the core logic of 'getNFTMetadata', including fetching the token URI, handling IPFS/base64 data, fetching metadata via HTTP, and caching the result.
async function handler(args: z.infer<typeof inputSchema>): Promise<ToolResult<NFTMetadataData>> { const { contractAddress, tokenId, chain } = args; const cacheKey = `nftmeta:${chain}:${contractAddress.toLowerCase()}:${tokenId}`; const cached = cache.get<NFTMetadataData>(cacheKey); if (cached.hit) return makeSuccess(chain, cached.data, true); // tokenId를 bigint로 변환 (유효성 검사) let tokenIdBigInt: bigint; try { tokenIdBigInt = BigInt(tokenId); } catch { return makeError(`Invalid tokenId: '${tokenId}' is not a valid integer`, "INVALID_INPUT"); } try { const client = getClient(chain); // 1단계: 컨트랙트에서 tokenURI 조회 const rawURI = await client.readContract({ address: contractAddress as `0x${string}`, abi: ERC721_TOKEN_URI_ABI, functionName: "tokenURI", args: [tokenIdBigInt], }) as string; // IPFS URI 처리 const resolvedURI = resolveURI(rawURI); // data URI (base64 인코딩된 JSON) 처리 if (resolvedURI.startsWith("data:application/json")) { try { const base64Part = resolvedURI.split(",")[1]; const jsonStr = Buffer.from(base64Part, "base64").toString("utf-8"); const meta = JSON.parse(jsonStr); const data: NFTMetadataData = { contractAddress, tokenId, tokenURI: rawURI, name: meta.name, description: meta.description, image: meta.image ? resolveURI(meta.image) : undefined, attributes: meta.attributes, }; cache.set(cacheKey, data, NFT_METADATA_CACHE_TTL); return makeSuccess(chain, data, false); } catch { return makeError(`Failed to parse base64-encoded metadata for tokenId ${tokenId}`, "RPC_ERROR"); } } // 2단계: SSRF 방지 — 내부 네트워크 차단 if (!isAllowedURL(resolvedURI)) { return makeError("Metadata URI points to a blocked or invalid address", "INVALID_INPUT"); } // URI로 HTTP 요청하여 메타데이터 JSON 취득 let metaJson: Record<string, unknown>; try { const response = await fetch(resolvedURI, { headers: { Accept: "application/json" }, signal: AbortSignal.timeout(10000), // 10초 타임아웃 }); if (!response.ok) { return makeError( `Failed to fetch metadata from URI: HTTP ${response.status}`, "API_ERROR", ); } const contentType = response.headers.get("content-type") ?? ""; if (!contentType.includes("json") && !contentType.includes("text")) { return makeError( `Unexpected content-type '${contentType}' from metadata URI`, "API_ERROR", ); } const text = await response.text(); try { metaJson = JSON.parse(text); } catch { return makeError(`Metadata URI did not return valid JSON for tokenId ${tokenId}`, "API_ERROR"); } } catch (err) { // fetch 자체 실패 (네트워크 오류, 타임아웃 등) if (err instanceof Error && (err.name === "AbortError" || err.message.includes("timeout"))) { return makeError(`Metadata fetch timed out for tokenId ${tokenId}`, "API_ERROR"); } return makeError(`Failed to fetch metadata: ${sanitizeError(err)}`, "API_ERROR"); } // 3단계: 메타데이터 필드 추출 및 image URI 정규화 const data: NFTMetadataData = { contractAddress, tokenId, tokenURI: rawURI, name: typeof metaJson.name === "string" ? metaJson.name : undefined, description: typeof metaJson.description === "string" ? metaJson.description : undefined, image: typeof metaJson.image === "string" ? resolveURI(metaJson.image) : undefined, attributes: Array.isArray(metaJson.attributes) ? (metaJson.attributes as NFTAttribute[]) : undefined, }; cache.set(cacheKey, data, NFT_METADATA_CACHE_TTL); return makeSuccess(chain, data, false); } catch (err) { return makeError(`Failed to fetch NFT metadata: ${sanitizeError(err)}`, "RPC_ERROR"); } } - src/tools/getNFTMetadata.ts:48-52 (schema)Input schema definition for the 'getNFTMetadata' tool using Zod.
const inputSchema = z.object({ contractAddress: z.string().describe("NFT 컨트랙트 주소 (0x...)"), tokenId: z.string().describe("조회할 토큰 ID"), chain: z.enum(SUPPORTED_CHAINS).default("ethereum").describe("EVM 체인"), }); - src/tools/getNFTMetadata.ts:169-179 (registration)Registration function for the 'getNFTMetadata' tool, exposing the tool to the MCP server.
export function register(server: McpServer) { server.tool( "getNFTMetadata", "ERC-721 NFT의 메타데이터(이름, 설명, 이미지, 속성)를 조회합니다. IPFS URI 자동 변환 지원", 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) }] }; }, ); }