get_stamp_dependencies
Map hierarchical dependencies for recursive stamps to visualize relationships and referenced stamps in various output formats.
Instructions
Create a hierarchical dependency graph for a recursive stamp, showing all referenced stamps and their relationships
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| stamp_id | Yes | The ID or CPID of the stamp to map dependencies for | |
| max_depth | No | Maximum depth to traverse dependencies | |
| include_metadata | No | Include full stamp metadata for each dependency | |
| format | No | Output format for the dependency structure | tree |
| resolve_all | No | Attempt to resolve all dependencies (may be slow for deep graphs) |
Implementation Reference
- src/tools/stamp-analysis.ts:472-942 (handler)GetStampDependenciesTool class: the main handler implementing the tool's execute() method. Resolves stamp ID/CPID, parses dependencies recursively up to max_depth, builds tree/graph structure, handles circular references, and supports multiple output formats (tree, graph, mermaid, json).export class GetStampDependenciesTool extends BaseTool< z.input<typeof GetStampDependenciesParamsSchema>, GetStampDependenciesParams > { public readonly name = 'get_stamp_dependencies'; public readonly description = 'Create a hierarchical dependency graph for a recursive stamp, showing all referenced stamps and their relationships'; public readonly inputSchema: MCPTool['inputSchema'] = { type: 'object', properties: { stamp_id: { type: ['number', 'string'], description: 'The ID or CPID of the stamp to map dependencies for', }, max_depth: { type: 'number', description: 'Maximum depth to traverse dependencies', default: 5, minimum: 1, maximum: 10, }, include_metadata: { type: 'boolean', description: 'Include full stamp metadata for each dependency', default: true, }, format: { type: 'string', enum: ['tree', 'graph', 'mermaid', 'json'], description: 'Output format for the dependency structure', default: 'tree', }, resolve_all: { type: 'boolean', description: 'Attempt to resolve all dependencies (may be slow for deep graphs)', default: true, }, }, required: ['stamp_id'], }; public readonly schema = GetStampDependenciesParamsSchema; public readonly metadata = { version: '1.0.0', tags: ['stamps', 'dependencies', 'visualization', 'mapping'], 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: GetStampDependenciesParams, context?: ToolContext ): Promise<ToolResponse> { try { context?.logger?.info('Executing get_stamp_dependencies tool', { params }); // Validate parameters const validatedParams = this.validateParams(params); // Resolve the root stamp const rootStamp = await this.resolveStamp(validatedParams.stamp_id, context); // Build the dependency tree const dependencyTree = await this.buildDependencyTree(rootStamp, validatedParams, context); // Format the output based on requested format const formattedOutput = this.formatDependencyTree( dependencyTree, validatedParams.format, context ); // Return both formatted output and raw data return multiResponse( { type: 'text', text: formattedOutput }, { type: 'text', text: `\nRaw Dependency Data:\n${JSON.stringify(dependencyTree, null, 2)}` } ); } catch (error) { context?.logger?.error('Error executing get_stamp_dependencies tool', { error }); if (error instanceof ValidationError) { throw error; } if (error instanceof ToolExecutionError) { throw error; } if (error instanceof Error) { throw new ToolExecutionError(error.message, this.name, error); } throw new ToolExecutionError('Failed to map stamp dependencies', this.name, error); } } /** * Resolve stamp by ID or CPID */ private async resolveStamp(stampId: string | number, context?: ToolContext): Promise<Stamp> { try { if (typeof stampId === 'number') { return await this.apiClient.getStamp(stampId); } const identifier = isStampIdentifier(stampId); if (identifier.type === 'id') { return await this.apiClient.getStamp(identifier.value as number); } else { // Search for CPID and get full stamp data 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); } const stampId = searchResults[0].stamp; if (!stampId) { throw new ToolExecutionError(`Invalid stamp ID for CPID: ${identifier.value}`, this.name); } return await this.apiClient.getStamp(stampId); } } 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 ); } } /** * Build a complete dependency tree for a stamp */ private async buildDependencyTree( rootStamp: Stamp, params: GetStampDependenciesParams, context?: ToolContext, visited: Set<string> = new Set(), currentDepth: number = 0 ): Promise<any> { const rootCpid = rootStamp.cpid; // Prevent infinite recursion if (visited.has(rootCpid) || currentDepth >= params.max_depth) { return { root: this.createTreeNode(rootStamp, [], currentDepth, params.include_metadata), nodes: new Map(), edges: [], totalNodes: 1, maxDepth: currentDepth, circularReferences: visited.has(rootCpid) ? [rootCpid] : [], }; } visited.add(rootCpid); context?.logger?.debug('Building dependency tree', { cpid: rootCpid, depth: currentDepth, visited: visited.size, }); // Parse dependencies from stamp content let dependencies: StampDependency[] = []; if (rootStamp.stamp_base64) { try { const rawContent = Buffer.from(rootStamp.stamp_base64, 'base64').toString('utf8'); const parsedJs = this.parser.parseJavaScript(rawContent); dependencies = parsedJs.dependencies; } catch (error) { context?.logger?.warn('Failed to parse stamp content', { cpid: rootCpid, error }); } } // Resolve child dependencies if requested const childNodes: any[] = []; const allNodes = new Map<string, any>(); const allEdges: Array<{ from: string; to: string; type: string }> = []; const circularRefs: string[] = []; let totalNodes = 1; let maxDepth = currentDepth; if (params.resolve_all && dependencies.length > 0) { for (const dep of dependencies) { // Skip HTML data dependencies (not actual stamps) if (dep.type === 'html' || !dep.cpid || !dep.cpid.match(/^A[0-9]+$/)) { continue; } try { // Check for circular reference if (visited.has(dep.cpid)) { circularRefs.push(dep.cpid); context?.logger?.warn('Circular dependency detected', { from: rootCpid, to: dep.cpid, }); continue; } // Resolve the dependency const searchResults = await this.apiClient.searchStamps({ cpid: dep.cpid }); if (searchResults.length > 0) { const depStamp = searchResults[0]; const depStampId = depStamp.stamp; if (depStampId) { const fullDepStamp = await this.apiClient.getStamp(depStampId); // Recursively build child tree const childTree = await this.buildDependencyTree( fullDepStamp, params, context, new Set(visited), currentDepth + 1 ); childNodes.push(childTree.root); // Merge child tree data childTree.nodes.forEach((node: any, cpid: string) => allNodes.set(cpid, node)); allEdges.push(...childTree.edges); circularRefs.push(...childTree.circularReferences); totalNodes += childTree.totalNodes; maxDepth = Math.max(maxDepth, childTree.maxDepth); // Add edge from root to child allEdges.push({ from: rootCpid, to: dep.cpid, type: dep.loadMethod, }); } } } catch (error) { context?.logger?.warn('Failed to resolve dependency', { cpid: dep.cpid, error: error instanceof Error ? error.message : String(error), }); } } } // Create the root node const rootNode = this.createTreeNode( rootStamp, childNodes, currentDepth, params.include_metadata ); allNodes.set(rootCpid, rootNode); return { root: rootNode, nodes: allNodes, edges: allEdges, totalNodes, maxDepth, circularReferences: [...new Set(circularRefs)], }; } /** * Create a tree node from stamp data */ private createTreeNode( stamp: Stamp, children: any[], depth: number, includeMetadata: boolean ): any { const node: any = { cpid: stamp.cpid, stampId: stamp.stamp || 0, depth, children, metadata: includeMetadata ? { creator: stamp.creator, mimetype: stamp.stamp_mimetype, blockIndex: stamp.block_index, txHash: stamp.tx_hash, supply: stamp.supply || 0, locked: Boolean(stamp.locked), } : undefined, }; return node; } /** * Format dependency tree based on requested output format */ private formatDependencyTree(tree: any, format: string, context?: ToolContext): string { switch (format) { case 'tree': return this.formatAsTree(tree); case 'mermaid': return this.formatAsMermaid(tree); case 'graph': return this.formatAsGraph(tree); case 'json': return JSON.stringify(tree, null, 2); default: return this.formatAsTree(tree); } } /** * Format dependency tree as a tree structure */ private formatAsTree(tree: any): string { const lines: string[] = []; lines.push('š³ Stamp Dependency Tree'); lines.push('========================'); lines.push(''); // Summary lines.push(`š Tree Summary:`); lines.push(` Total Nodes: ${tree.totalNodes}`); lines.push(` Max Depth: ${tree.maxDepth}`); lines.push(` Edges: ${tree.edges.length}`); if (tree.circularReferences.length > 0) { lines.push(` ā ļø Circular References: ${tree.circularReferences.length}`); } lines.push(''); // Tree structure lines.push('šļø Dependency Structure:'); this.renderTreeNode(tree.root, lines, '', true); // Circular references warning if (tree.circularReferences.length > 0) { lines.push(''); lines.push('ā ļø Circular References Detected:'); tree.circularReferences.forEach((cpid: string) => { lines.push(` - ${cpid}`); }); } return lines.join('\n'); } /** * Recursively render tree nodes */ private renderTreeNode(node: any, lines: string[], prefix: string, isLast: boolean): void { const connector = isLast ? 'āāā ' : 'āāā '; const stampInfo = node.metadata ? ` (ID: ${node.stampId}, Creator: ${node.metadata.creator?.substring(0, 20)}...)` : ` (ID: ${node.stampId})`; lines.push(`${prefix}${connector}š ${node.cpid}${stampInfo}`); if (node.metadata) { const childPrefix = prefix + (isLast ? ' ' : 'ā '); lines.push(`${childPrefix}āā Type: ${node.metadata.mimetype}`); lines.push(`${childPrefix}āā Block: ${node.metadata.blockIndex}`); lines.push(`${childPrefix}āā Supply: ${node.metadata.supply}`); } // Render children const newPrefix = prefix + (isLast ? ' ' : 'ā '); node.children.forEach((child: any, index: number) => { const childIsLast = index === node.children.length - 1; this.renderTreeNode(child, lines, newPrefix, childIsLast); }); } /** * Format dependency tree as Mermaid diagram */ private formatAsMermaid(tree: any): string { const lines: string[] = []; lines.push('```mermaid'); lines.push('graph TD'); lines.push(''); // Add nodes tree.nodes.forEach((node: any, cpid: string) => { const shortCpid = cpid.substring(0, 8) + '...'; const nodeLabel = `${shortCpid}<br/>ID: ${node.stampId}`; lines.push(` ${this.sanitizeMermaidId(cpid)}["${nodeLabel}"]`); }); lines.push(''); // Add edges tree.edges.forEach((edge: any) => { const fromId = this.sanitizeMermaidId(edge.from); const toId = this.sanitizeMermaidId(edge.to); lines.push(` ${fromId} -->|${edge.type}| ${toId}`); }); // Add circular reference warnings if (tree.circularReferences.length > 0) { lines.push(''); lines.push(' %% Circular references detected:'); tree.circularReferences.forEach((cpid: string) => { lines.push(` %% - ${cpid}`); }); } lines.push('```'); lines.push(''); lines.push( `**Graph Statistics:** ${tree.totalNodes} nodes, ${tree.edges.length} edges, max depth ${tree.maxDepth}` ); return lines.join('\n'); } /** * Format dependency tree as adjacency list */ private formatAsGraph(tree: any): string { const lines: string[] = []; lines.push('š Dependency Graph (Adjacency List)'); lines.push('===================================='); lines.push(''); tree.nodes.forEach((node: any, cpid: string) => { lines.push(`š ${cpid} (ID: ${node.stampId}, Depth: ${node.depth})`); // Find outgoing edges const outgoingEdges = tree.edges.filter((edge: any) => edge.from === cpid); if (outgoingEdges.length > 0) { outgoingEdges.forEach((edge: any) => { lines.push(` āā ${edge.type} ā ${edge.to}`); }); } else { lines.push(' āā (no dependencies)'); } lines.push(''); }); return lines.join('\n'); } /** * Sanitize CPID for Mermaid node IDs */ private sanitizeMermaidId(cpid: string): string { return cpid.replace(/[^a-zA-Z0-9]/g, '_'); } }
- Input validation schema GetStampDependenciesParamsSchema defining parameters like stamp_id, max_depth, format etc. Used by the tool's inputSchema and Zod validation.* Schema for get_stamp_dependencies parameters */ export const GetStampDependenciesParamsSchema = z.object({ stamp_id: z .union([z.number(), z.string()]) .describe('The ID or CPID of the stamp to map dependencies for'), max_depth: z .number() .min(1) .max(10) .default(5) .describe('Maximum depth to traverse dependencies'), include_metadata: z .boolean() .default(true) .describe('Include full stamp metadata for each dependency'), format: z .enum(['tree', 'graph', 'mermaid', 'json']) .default('tree') .describe('Output format for the dependency structure'), resolve_all: z .boolean() .default(true) .describe('Attempt to resolve all dependencies (may be slow for deep graphs)'), });
- src/tools/stamp-analysis.ts:1763-1767 (registration)Registration in stampAnalysisTools export object, making the tool class available for instantiation.export const stampAnalysisTools = { analyze_stamp_code: AnalyzeStampCodeTool, get_stamp_dependencies: GetStampDependenciesTool, analyze_stamp_patterns: AnalyzeStampPatternsTool, };
- src/tools/index.ts:66-69 (registration)Tool name 'get_stamp_dependencies' registered in getAvailableToolNames() function listing all available tools.'analyze_stamp_code', 'get_stamp_dependencies', 'analyze_stamp_patterns', ];
- src/tools/index.ts:101-101 (registration)Tool included in toolMetadata.analysis.tools array for discovery and categorization.tools: ['analyze_stamp_code', 'get_stamp_dependencies', 'analyze_stamp_patterns'],