Skip to main content
Glama
stampchain-io

Stampchain MCP Server

Official

analyze_stamp_code

Analyze JavaScript code structure, dependencies, and patterns in Bitcoin Stamps to identify security risks, performance issues, and recursive relationships.

Instructions

Analyze the code structure and dependencies of a recursive stamp, including JavaScript parsing, dependency resolution, and pattern detection

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
stamp_idYesThe ID or CPID of the stamp to analyze
include_dependenciesNoWhether to analyze referenced stamps
max_depthNoMaximum depth for dependency analysis
include_raw_contentNoWhether to include raw stamp content in response
include_security_analysisNoWhether to include security analysis
include_performance_analysisNoWhether to include performance analysis

Implementation Reference

  • AnalyzeStampCodeTool class: the main handler implementing the tool logic. The async execute method (lines 100-134) orchestrates stamp fetching, JavaScript parsing with RecursiveStampParser, dependency resolution, security/performance analysis, and response formatting.
    export class AnalyzeStampCodeTool extends BaseTool<
      z.input<typeof AnalyzeStampCodeParamsSchema>,
      AnalyzeStampCodeParams
    > {
      public readonly name = 'analyze_stamp_code';
    
      public readonly description =
        'Analyze the code structure and dependencies of a recursive stamp, including JavaScript parsing, dependency resolution, and pattern detection';
    
      public readonly inputSchema: MCPTool['inputSchema'] = {
        type: 'object',
        properties: {
          stamp_id: {
            type: ['number', 'string'],
            description: 'The ID or CPID of the stamp to analyze',
          },
          include_dependencies: {
            type: 'boolean',
            description: 'Whether to analyze referenced stamps',
            default: true,
          },
          max_depth: {
            type: 'number',
            description: 'Maximum depth for dependency analysis',
            default: 3,
            minimum: 1,
            maximum: 10,
          },
          include_raw_content: {
            type: 'boolean',
            description: 'Whether to include raw stamp content in response',
            default: false,
          },
          include_security_analysis: {
            type: 'boolean',
            description: 'Whether to include security analysis',
            default: true,
          },
          include_performance_analysis: {
            type: 'boolean',
            description: 'Whether to include performance analysis',
            default: true,
          },
        },
        required: ['stamp_id'],
      };
    
      public readonly schema = AnalyzeStampCodeParamsSchema;
    
      public readonly metadata = {
        version: '1.0.0',
        tags: ['stamps', 'analysis', 'recursive', 'dependencies'],
        requiresNetwork: true,
        apiDependencies: ['stampchain'],
      };
    
      private apiClient: StampchainClient;
      private parser: RecursiveStampParser;
    
      constructor(apiClient?: StampchainClient) {
        super();
        this.apiClient = apiClient || new StampchainClient();
        this.parser = new RecursiveStampParser();
      }
    
      public async execute(
        params: AnalyzeStampCodeParams,
        context?: ToolContext
      ): Promise<ToolResponse> {
        try {
          context?.logger?.info('Executing analyze_stamp_code tool', { params });
    
          // Validate parameters
          const validatedParams = this.validateParams(params);
    
          // Resolve stamp ID from CPID if needed
          const stamp = await this.resolveStamp(validatedParams.stamp_id, context);
    
          // Analyze the stamp
          const analysis = await this.analyzeStamp(stamp, validatedParams, context);
    
          // Format response
          const formattedResponse = this.formatAnalysisResponse(analysis, validatedParams);
    
          return multiResponse(
            { type: 'text', text: formattedResponse },
            { type: 'text', text: `\nDetailed Analysis:\n${JSON.stringify(analysis, null, 2)}` }
          );
        } catch (error) {
          // Handle Zod validation errors with new standardized pattern
          if (error instanceof z.ZodError) {
            const result = handleValidationError(error, this.name, params, context);
            return result.response;
          }
    
          // Handle all other errors with standardized pattern
          const result = handleMCPError(error, this.name, 'stamp_analysis', params, context);
          return result.response;
        }
      }
    
      /**
       * Resolve stamp by ID or CPID
       */
      private async resolveStamp(stampId: string | number, context?: ToolContext): Promise<Stamp> {
        try {
          // If it's a number, use it directly
          if (typeof stampId === 'number') {
            return await this.apiClient.getStamp(stampId);
          }
    
          // If it's a string, determine if it's a numeric ID or CPID
          try {
            const identifier = isStampIdentifier(stampId);
    
            if (identifier.type === 'id') {
              // It's a numeric ID
              return await this.apiClient.getStamp(identifier.value as number);
            } else {
              // It's a CPID, search for it first to get the stamp ID
              context?.logger?.debug('Searching for stamp by CPID', { cpid: identifier.value });
              const searchResults = await this.apiClient.searchStamps({
                cpid: identifier.value as string,
              });
    
              if (searchResults.length === 0) {
                throw new ToolExecutionError(
                  `No stamp found with CPID: ${identifier.value}`,
                  this.name
                );
              }
    
              // Now get the full stamp data with base64 content using the stamp ID
              const stampId = searchResults[0].stamp;
              if (!stampId) {
                throw new ToolExecutionError(
                  `Invalid stamp ID for CPID: ${identifier.value}`,
                  this.name
                );
              }
    
              context?.logger?.debug('Getting full stamp data', { stampId });
              return await this.apiClient.getStamp(stampId);
            }
          } catch (validationError) {
            throw new ToolExecutionError(
              `Invalid stamp identifier: ${validationError instanceof Error ? validationError.message : String(validationError)}`,
              this.name,
              validationError
            );
          }
        } catch (error) {
          if (error instanceof ToolExecutionError) {
            throw error;
          }
          throw new ToolExecutionError(
            `Failed to resolve stamp: ${error instanceof Error ? error.message : String(error)}`,
            this.name,
            error
          );
        }
      }
    
      /**
       * Analyze a stamp for recursive patterns and dependencies
       */
      private async analyzeStamp(
        stamp: Stamp,
        params: AnalyzeStampCodeParams,
        context?: ToolContext
      ): Promise<StampAnalysisResult> {
        context?.logger?.debug('Analyzing stamp', { stampId: stamp.stamp, cpid: stamp.cpid });
    
        // Get the stamp content
        if (!stamp.stamp_base64) {
          throw new ToolExecutionError(
            `Stamp ${stamp.stamp} does not have base64 content available`,
            this.name
          );
        }
    
        // Decode the content
        const rawContent = Buffer.from(stamp.stamp_base64, 'base64').toString('utf8');
        context?.logger?.debug('Decoded stamp content', { contentLength: rawContent.length });
    
        // Parse the JavaScript code
        const parsedJs = this.parser.parseJavaScript(rawContent);
    
        // Analyze code structure
        const codeStructure = this.parser.analyzeCodeStructure(rawContent);
    
        // Resolve dependencies if requested
        let resolvedDependencies: StampDependency[] = parsedJs.dependencies;
        if (params.include_dependencies) {
          resolvedDependencies = await this.resolveDependencies(
            parsedJs.dependencies,
            params.max_depth,
            context
          );
        }
    
        // Perform security analysis if requested
        let security;
        if (params.include_security_analysis) {
          security = this.parser.analyzeCodeSecurity(rawContent);
        } else {
          security = {
            riskLevel: 'low' as const,
            risks: [],
            hasDangerousPatterns: false,
            isCodeSafe: true,
          };
        }
    
        // Perform performance analysis if requested
        let performance;
        if (params.include_performance_analysis) {
          performance = this.parser.analyzeCodePerformance(rawContent, resolvedDependencies);
        } else {
          performance = {
            complexityScore: 0,
            dependencyCount: resolvedDependencies.length,
            maxDependencyDepth: 0,
          };
        }
    
        // Process HTML data if present
        let decodedContent: string | undefined;
        for (const dep of resolvedDependencies) {
          if (dep.type === 'html' && dep.cpid) {
            try {
              decodedContent = await this.parser.decompressHtmlData(dep.cpid);
              break; // Only process the first HTML dependency for now
            } catch (error) {
              context?.logger?.warn('Failed to decompress HTML data', { error });
            }
          }
        }
    
        const result: StampAnalysisResult = {
          stamp: {
            id: stamp.stamp || 0,
            cpid: stamp.cpid,
            creator: stamp.creator,
            mimetype: stamp.stamp_mimetype,
            blockIndex: stamp.block_index,
            txHash: stamp.tx_hash,
          },
          codeStructure,
          dependencies: resolvedDependencies,
          patterns: parsedJs.patterns,
          security,
          performance,
          rawContent: params.include_raw_content ? rawContent : undefined,
          decodedContent,
          analysisTimestamp: Date.now(),
        };
    
        context?.logger?.info('Stamp analysis completed', {
          stampId: stamp.stamp,
          dependencyCount: resolvedDependencies.length,
          patternCount: parsedJs.patterns.length,
          isRecursive: codeStructure.isRecursive,
        });
    
        return result;
      }
    
      /**
       * Resolve stamp dependencies by looking up their metadata
       */
      private async resolveDependencies(
        dependencies: StampDependency[],
        maxDepth: number,
        context?: ToolContext,
        currentDepth: number = 0
      ): Promise<StampDependency[]> {
        if (currentDepth >= maxDepth) {
          return dependencies;
        }
    
        const resolved: StampDependency[] = [];
    
        for (const dep of dependencies) {
          const resolvedDep = { ...dep };
    
          // Only try to resolve CPID dependencies (not HTML data)
          if (dep.type !== 'html' && dep.cpid && dep.cpid.match(/^A[0-9]+$/)) {
            try {
              context?.logger?.debug('Resolving dependency', { cpid: dep.cpid });
              const searchResults = await this.apiClient.searchStamps({ cpid: dep.cpid });
    
              if (searchResults.length > 0) {
                resolvedDep.isResolved = true;
                resolvedDep.stampId = searchResults[0].stamp || undefined;
                resolvedDep.metadata = searchResults[0];
    
                context?.logger?.debug('Resolved dependency', {
                  cpid: dep.cpid,
                  stampId: searchResults[0].stamp,
                });
              } else {
                context?.logger?.warn('Could not resolve dependency', { cpid: dep.cpid });
              }
            } catch (error) {
              context?.logger?.warn('Error resolving dependency', { cpid: dep.cpid, error });
            }
          }
    
          resolved.push(resolvedDep);
        }
    
        return resolved;
      }
    
      /**
       * Format the analysis response for display
       */
      private formatAnalysisResponse(
        analysis: StampAnalysisResult,
        params: AnalyzeStampCodeParams
      ): string {
        const lines: string[] = [];
    
        lines.push(`πŸ” Recursive Stamp Analysis Report`);
        lines.push(`=====================================`);
        lines.push('');
    
        // Basic stamp info
        lines.push(`πŸ“„ Stamp Information:`);
        lines.push(`   ID: ${analysis.stamp.id}`);
        lines.push(`   CPID: ${analysis.stamp.cpid}`);
        lines.push(`   Creator: ${analysis.stamp.creator}`);
        lines.push(`   MIME Type: ${analysis.stamp.mimetype}`);
        lines.push(`   Block: ${analysis.stamp.blockIndex}`);
        lines.push('');
    
        // Code structure
        lines.push(`πŸ—οΈ  Code Structure:`);
        lines.push(`   Has JavaScript: ${analysis.codeStructure.hasJavaScript ? 'βœ…' : '❌'}`);
        lines.push(`   Has HTML: ${analysis.codeStructure.hasHTML ? 'βœ…' : '❌'}`);
        lines.push(
          `   Uses Append Framework: ${analysis.codeStructure.usesAppendFramework ? 'βœ…' : '❌'}`
        );
        lines.push(`   Is Recursive: ${analysis.codeStructure.isRecursive ? 'βœ…' : '❌'}`);
        lines.push(`   Has Async Loading: ${analysis.codeStructure.hasAsyncLoading ? 'βœ…' : '❌'}`);
        lines.push(`   Has Error Handling: ${analysis.codeStructure.hasErrorHandling ? 'βœ…' : '❌'}`);
        lines.push('');
    
        // Dependencies
        if (analysis.dependencies.length > 0) {
          lines.push(`πŸ”— Dependencies (${analysis.dependencies.length}):`);
          analysis.dependencies.forEach((dep, index) => {
            const status = dep.isResolved ? 'βœ…' : '❌';
            const stampInfo = dep.stampId ? ` (Stamp #${dep.stampId})` : '';
            lines.push(
              `   ${index + 1}. ${status} ${dep.cpid} [${dep.type}/${dep.loadMethod}]${stampInfo}`
            );
          });
          lines.push('');
        }
    
        // Patterns
        if (analysis.patterns.length > 0) {
          lines.push(`🎯 Detected Patterns (${analysis.patterns.length}):`);
          analysis.patterns.forEach((pattern, index) => {
            const confidence = Math.round(pattern.confidence * 100);
            lines.push(`   ${index + 1}. ${pattern.name} (${confidence}% confidence)`);
            lines.push(`      Category: ${pattern.category}`);
            lines.push(`      Description: ${pattern.description}`);
          });
          lines.push('');
        }
    
        // Security analysis
        if (params.include_security_analysis) {
          const riskIcon =
            analysis.security.riskLevel === 'low'
              ? '🟒'
              : analysis.security.riskLevel === 'medium'
                ? '🟑'
                : 'πŸ”΄';
          lines.push(`πŸ”’ Security Analysis:`);
          lines.push(`   Risk Level: ${riskIcon} ${analysis.security.riskLevel.toUpperCase()}`);
          lines.push(
            `   Code Safety: ${analysis.security.isCodeSafe ? 'βœ… Safe' : '⚠️ Potentially Unsafe'}`
          );
          if (analysis.security.risks.length > 0) {
            lines.push(`   Risks Found:`);
            analysis.security.risks.forEach((risk, index) => {
              lines.push(`     ${index + 1}. ${risk}`);
            });
          }
          lines.push('');
        }
    
        // Performance analysis
        if (params.include_performance_analysis) {
          const complexityIcon =
            analysis.performance.complexityScore <= 3
              ? '🟒'
              : analysis.performance.complexityScore <= 6
                ? '🟑'
                : 'πŸ”΄';
          lines.push(`⚑ Performance Analysis:`);
          lines.push(
            `   Complexity Score: ${complexityIcon} ${analysis.performance.complexityScore.toFixed(1)}/10`
          );
          lines.push(`   Dependency Count: ${analysis.performance.dependencyCount}`);
          lines.push(`   Max Dependency Depth: ${analysis.performance.maxDependencyDepth}`);
          if (analysis.performance.estimatedLoadTime) {
            lines.push(
              `   Estimated Load Time: ${analysis.performance.estimatedLoadTime.toFixed(0)}ms`
            );
          }
          lines.push('');
        }
    
        // Decoded HTML content
        if (analysis.decodedContent) {
          lines.push(`πŸ“ Decoded HTML Content:`);
          lines.push(`   Length: ${analysis.decodedContent.length} characters`);
          lines.push(
            `   Preview: ${analysis.decodedContent.substring(0, 200)}${analysis.decodedContent.length > 200 ? '...' : ''}`
          );
          lines.push('');
        }
    
        lines.push(`πŸ“Š Analysis completed at ${new Date(analysis.analysisTimestamp).toISOString()}`);
    
        return lines.join('\n');
      }
    }
  • Zod schema defining input parameters for analyze_stamp_code tool, including stamp_id, analysis options like include_dependencies, max_depth, etc.
    export const AnalyzeStampCodeParamsSchema = z.object({
      stamp_id: z.union([z.number(), z.string()]),
      include_dependencies: z.boolean().default(true),
      max_depth: z.number().min(1).max(10).default(3),
      include_raw_content: z.boolean().default(false),
      include_security_analysis: z.boolean().default(true),
      include_performance_analysis: z.boolean().default(true),
    });
  • Registration of analyze_stamp_code tool in the stampAnalysisTools export object.
    export const stampAnalysisTools = {
      analyze_stamp_code: AnalyzeStampCodeTool,
      get_stamp_dependencies: GetStampDependenciesTool,
      analyze_stamp_patterns: AnalyzeStampPatternsTool,
    };
  • Import of stamp analysis tools including analyze_stamp_code from stamp-analysis.ts into central index.ts
    import { createStampAnalysisTools, stampAnalysisTools } from './stamp-analysis.js';
  • Instantiation of analysis tools including analyze_stamp_code in createAllTools factory function.
    const analysis = createStampAnalysisTools(client);

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/stampchain-io/stampchain-mcp'

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