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
| Name | Required | Description | Default |
|---|---|---|---|
| stamp_id | Yes | The ID or CPID of the stamp to analyze | |
| include_dependencies | No | Whether to analyze referenced stamps | |
| max_depth | No | Maximum depth for dependency analysis | |
| include_raw_content | No | Whether to include raw stamp content in response | |
| include_security_analysis | No | Whether to include security analysis | |
| include_performance_analysis | No | Whether to include performance analysis |
Implementation Reference
- src/tools/stamp-analysis.ts:35-467 (handler)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), });
- src/tools/stamp-analysis.ts:1763-1767 (registration)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, };
- src/tools/index.ts:10-10 (registration)Import of stamp analysis tools including analyze_stamp_code from stamp-analysis.ts into central index.tsimport { createStampAnalysisTools, stampAnalysisTools } from './stamp-analysis.js';
- src/tools/index.ts:34-34 (registration)Instantiation of analysis tools including analyze_stamp_code in createAllTools factory function.const analysis = createStampAnalysisTools(client);