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);
Behavior2/5

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

With no annotations provided, the description carries full burden for behavioral disclosure. While it mentions analysis activities (parsing, resolution, detection), it doesn't describe what the tool actually returns, whether it's computationally intensive, if there are rate limits, or what 'security analysis' and 'performance analysis' entail. For a complex 6-parameter tool with no output schema, 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.

Conciseness4/5

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

The description is a single, efficient sentence that packs substantial information about the tool's scope. It front-loads the core purpose and lists key analysis components. While dense, every element serves a purpose in conveying what the tool does. No wasted words or redundant information.

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 analysis tool with 6 parameters, no annotations, and no output schema, the description is insufficient. It doesn't explain what format the analysis results take, what 'recursive stamp' means in this context, or how the different analysis components (security, performance) relate to each other. The agent would struggle to understand what to expect from using this tool.

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 all parameters are documented in the schema. The description doesn't add any parameter-specific information beyond what's already in the schema descriptions. It mentions 'dependency resolution' which relates to 'include_dependencies' and 'max_depth', but doesn't provide additional context about how these parameters interact or affect the analysis.

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 analyzes code structure and dependencies of a recursive stamp, specifying JavaScript parsing, dependency resolution, and pattern detection. It distinguishes itself from simpler sibling tools like 'get_stamp' or 'get_stamp_dependencies' by offering comprehensive analysis. However, it doesn't explicitly differentiate from 'analyze_stamp_patterns' which might overlap in pattern detection functionality.

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. With sibling tools like 'get_stamp_dependencies' and 'analyze_stamp_patterns' available, there's no indication of when this comprehensive analysis tool is preferable over more focused tools. No prerequisites, exclusions, or comparative context is mentioned.

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

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