Skip to main content
Glama

get_sip009_token_info

Retrieve comprehensive SIP-009 NFT details including ownership, metadata URI, and content for specific token IDs on the Stacks blockchain network.

Instructions

Get complete information about a specific SIP-009 NFT including owner, metadata URI, and actual metadata content.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
contractAddressYesThe contract address
contractNameYesThe contract name of the SIP-009 NFT collection
networkYesThe Stacks network
tokenIdYesThe NFT token ID

Implementation Reference

  • The main execution handler for the 'get_sip009_token_info' tool. Fetches NFT owner and token URI using parallel Hiro API read-only contract calls, parses results, fetches and displays metadata, and returns formatted information.
    export const getSIP009TokenInfoTool: Tool<undefined, typeof SIP009TokenInfoScheme> = {
      name: "get_sip009_token_info",
      description: "Get complete information about a specific SIP-009 NFT including owner, metadata URI, and actual metadata content.",
      parameters: SIP009TokenInfoScheme,
      execute: async (args, context) => {
        try {
          await recordTelemetry({ action: "get_sip009_token_info" }, context);
          
          // Get NFT information in parallel
          const [ownerResult, uriResult] = await Promise.all([
            callReadOnlyFunction(
              args.contractAddress,
              args.contractName,
              "get-owner",
              [`0x${args.tokenId.toString(16).padStart(32, '0')}`],
              args.network
            ),
            callReadOnlyFunction(
              args.contractAddress,
              args.contractName,
              "get-token-uri",
              [`0x${args.tokenId.toString(16).padStart(32, '0')}`],
              args.network
            ),
          ]);
          
          // Parse results
          const owner = ownerResult.okay && ownerResult.result !== 'none' 
            ? ownerResult.result.replace(/[()]/g, '').replace('some ', '') 
            : 'No owner (token may not exist)';
          
          const metadataUri = uriResult.okay && uriResult.result !== 'none' 
            ? uriResult.result.replace(/[()]/g, '').replace('some ', '').replace(/"/g, '')
            : null;
          
          let metadata = null;
          if (metadataUri) {
            metadata = await fetchMetadata(metadataUri);
          }
          
          return `# SIP-009 NFT Information
    
    **Contract**: ${args.contractAddress}.${args.contractName}
    **Token ID**: ${args.tokenId}
    **Network**: ${args.network}
    
    ## Ownership
    - **Current Owner**: ${owner}
    
    ## Metadata
    - **URI**: ${metadataUri || 'No metadata URI'}
    
    ${metadata ? `
    ## Metadata Content
    - **Name**: ${metadata.name || 'N/A'}
    - **Description**: ${metadata.description || 'N/A'}
    - **Image**: ${metadata.image || 'N/A'}
    - **External URL**: ${metadata.external_url || 'N/A'}
    
    ${metadata.attributes ? `
    ### Attributes
    ${metadata.attributes.map((attr: any) => `- **${attr.trait_type}**: ${attr.value}`).join('\n')}
    ` : ''}
    ` : metadata === null && metadataUri ? `
    ⚠️ **Metadata fetch failed** - URI may be inaccessible
    ` : ''}
    
    ## Contract Verification
    ✅ This contract implements the SIP-009 NFT standard trait.
    
    ## Transfer Notes
    - Post-conditions are REQUIRED for all transfers
    - Only the current owner can initiate transfers
    - Use native Stacks post-conditions for security`;
          
        } catch (error) {
          return `❌ Failed to get SIP-009 NFT info: ${error}`;
        }
      },
    };
  • Input schema (Zod) defining required parameters: contractAddress, contractName, tokenId, and network (mainnet/testnet/devnet).
    const SIP009TokenInfoScheme = z.object({
      contractAddress: z.string().describe("The contract address"),
      contractName: z.string().describe("The contract name of the SIP-009 NFT collection"),
      tokenId: z.number().describe("The NFT token ID"),
      network: NetworkScheme.describe("The Stacks network"),
    });
  • Registers the getSIP009TokenInfoTool with the FastMCP server instance.
    server.addTool(getSIP009TokenInfoTool);
  • Helper function to perform read-only contract function calls via Hiro API (stacks-blockchain explorer). Used to fetch 'get-owner' and 'get-token-uri'.
    async function callReadOnlyFunction(
      contractAddress: string,
      contractName: string,
      functionName: string,
      functionArgs: any[],
      network: string
    ): Promise<any> {
      const apiUrl = network === "mainnet" 
        ? "https://api.hiro.so" 
        : "https://api.testnet.hiro.so";
      
      try {
        const response = await fetch(
          `${apiUrl}/v2/contracts/call-read/${contractAddress}/${contractName}/${functionName}`,
          {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
              sender: contractAddress,
              arguments: functionArgs,
            }),
          }
        );
        
        if (!response.ok) {
          const data: any = await response.json();
          throw new Error(data.error || `HTTP ${response.status}`);
        }
        
        return await response.json();
      } catch (error) {
        throw new Error(`Failed to call ${functionName}: ${error}`);
      }
    }
  • Helper function to fetch and parse NFT metadata JSON from the token URI, with IPFS support and 5-second timeout.
    async function fetchMetadata(uri: string): Promise<any> {
      try {
        // Handle IPFS URIs
        if (uri.startsWith('ipfs://')) {
          uri = uri.replace('ipfs://', 'https://ipfs.io/ipfs/');
        }
        
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), 5000);
        
        const response = await fetch(uri, { signal: controller.signal });
        clearTimeout(timeoutId);
        
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}`);
        }
        
        return await response.json();
      } catch (error) {
        return null;
      }
    }
Behavior2/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It states what data is retrieved but doesn't mention potential limitations like rate limits, authentication requirements, error conditions, or whether this is a read-only operation. For a tool with 4 required parameters and no annotation coverage, this leaves significant behavioral gaps.

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?

The description is a single, well-structured sentence that efficiently communicates the core functionality. It's front-loaded with the main purpose and specifies exactly what information is retrieved without unnecessary elaboration.

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

Completeness3/5

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

For a read operation with 100% schema coverage but no output schema, the description adequately covers what data is retrieved. However, without annotations or output schema, it doesn't specify the return format, structure, or potential limitations. The description is complete enough for basic understanding but lacks depth for reliable agent usage.

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%, so the schema already documents all 4 parameters thoroughly. The description doesn't add any parameter-specific details beyond what's in the schema descriptions. The baseline of 3 is appropriate when the schema does the heavy lifting for parameter documentation.

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

Purpose4/5

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

The description clearly states the tool's purpose with a specific verb ('Get') and resource ('SIP-009 NFT'), and specifies what information it retrieves (owner, metadata URI, metadata content). However, it doesn't explicitly differentiate from sibling tools like 'get_sip009_collection_info' or 'get_sip010_info', which handle different NFT standards or collection-level data.

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?

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention sibling tools like 'get_sip009_collection_info' for collection-level info or 'get_sip010_info' for fungible tokens, nor does it specify prerequisites or exclusions for usage.

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/exponentlabshq/stacks-clarity-mcp'

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