@yamo/mcp-server 1.3.10 → 1.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +271 -248
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -47,19 +47,45 @@ const crypto_1 = __importDefault(require("crypto"));
47
47
  const path_1 = __importDefault(require("path"));
48
48
  const pkg = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '../package.json'), 'utf8'));
49
49
  dotenv.config();
50
+ // Constants
51
+ const GENESIS_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000";
52
+ const TOOL_NAMES = {
53
+ SUBMIT_BLOCK: "yamo_submit_block",
54
+ GET_BLOCK: "yamo_get_block",
55
+ GET_LATEST_BLOCK: "yamo_get_latest_block",
56
+ AUDIT_BLOCK: "yamo_audit_block",
57
+ VERIFY_BLOCK: "yamo_verify_block",
58
+ };
59
+ const LOG_PREFIX = {
60
+ DEBUG: "[DEBUG]",
61
+ INFO: "[INFO]",
62
+ ERROR: "[ERROR]",
63
+ };
64
+ const VALIDATION_RULES = {
65
+ BYTES32_PATTERN: /^0x[a-fA-F0-9]{64}$/,
66
+ ETH_ADDRESS_PATTERN: /^0x[a-fA-F0-9]{40}$/,
67
+ };
50
68
  // Validation helpers
51
69
  function validateBytes32(value, fieldName) {
52
- if (!value.match(/^0x[a-fA-F0-9]{64}$/)) {
70
+ if (!value.match(VALIDATION_RULES.BYTES32_PATTERN)) {
53
71
  throw new Error(`${fieldName} must be a valid bytes32 hash (0x + 64 hex chars). ` +
54
72
  `Received: ${value.substring(0, 20)}...` +
55
73
  `\nDo NOT include algorithm prefixes like "sha256:"`);
56
74
  }
57
75
  }
58
76
  function validateEthereumAddress(address, fieldName) {
59
- if (!address || !address.match(/^0x[a-fA-F0-9]{40}$/)) {
77
+ if (!address || !address.match(VALIDATION_RULES.ETH_ADDRESS_PATTERN)) {
60
78
  throw new Error(`${fieldName} must be a valid Ethereum address (0x + 40 hex characters)`);
61
79
  }
62
80
  }
81
+ function validateBlockId(blockId) {
82
+ if (!blockId)
83
+ throw new Error("blockId is required");
84
+ const parts = blockId.split('_');
85
+ if (parts.length < 2) {
86
+ throw new Error(`blockId must follow format {origin}_{workflow} (e.g., 'claude_chain'). Received: ${blockId}`);
87
+ }
88
+ }
63
89
  function validateEnvironment() {
64
90
  const requiredEnvVars = ['CONTRACT_ADDRESS', 'RPC_URL', 'PRIVATE_KEY'];
65
91
  const missing = requiredEnvVars.filter(v => !process.env[v]);
@@ -275,6 +301,238 @@ class YamoMcpServer {
275
301
  this.chain = new core_1.YamoChainClient();
276
302
  this.setupHandlers();
277
303
  }
304
+ // Response formatting helpers
305
+ createSuccessResponse(data) {
306
+ return {
307
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
308
+ };
309
+ }
310
+ createErrorResponse(error, isError = true) {
311
+ return {
312
+ content: [{ type: "text", text: JSON.stringify(error, null, 2) }],
313
+ isError,
314
+ };
315
+ }
316
+ createTextResponse(text) {
317
+ return { content: [{ type: "text", text }] };
318
+ }
319
+ // Logging helper
320
+ log(level, message) {
321
+ const prefix = LOG_PREFIX[level];
322
+ console.error(`${prefix} ${message}`);
323
+ }
324
+ // Cache update helper
325
+ updateLatestBlockCache(contentHash) {
326
+ this.latestContentHash = contentHash;
327
+ this.log('INFO', `Updated latestContentHash cache: ${contentHash}`);
328
+ }
329
+ // Block formatting helper
330
+ formatBlockResponse(block) {
331
+ return {
332
+ blockId: block.blockId,
333
+ previousBlock: block.previousBlock,
334
+ agentAddress: block.agentAddress,
335
+ contentHash: block.contentHash,
336
+ timestamp: block.timestamp,
337
+ timestampISO: new Date(block.timestamp * 1000).toISOString(),
338
+ consensusType: block.consensusType,
339
+ ledger: block.ledger,
340
+ ipfsCID: block.ipfsCID || null
341
+ };
342
+ }
343
+ // File security validation
344
+ validateFileSecurity(filePath) {
345
+ const realPath = fs_1.default.realpathSync(filePath);
346
+ const allowedDir = fs_1.default.realpathSync(process.cwd());
347
+ const stats = fs_1.default.lstatSync(filePath);
348
+ if (stats.isSymbolicLink()) {
349
+ throw new Error(`Symbolic links are not allowed: ${filePath}`);
350
+ }
351
+ if (!realPath.startsWith(allowedDir)) {
352
+ throw new Error(`File path outside allowed directory: ${filePath}`);
353
+ }
354
+ }
355
+ // File processing helper
356
+ async processSingleFile(file) {
357
+ if (typeof file.content === 'string' && fs_1.default.existsSync(file.content)) {
358
+ this.validateFileSecurity(file.content);
359
+ this.log('DEBUG', `Auto-reading file from path: ${file.content}`);
360
+ const content = await fs_1.default.promises.readFile(fs_1.default.realpathSync(file.content), 'utf8');
361
+ return { name: file.name, content };
362
+ }
363
+ return file;
364
+ }
365
+ // Previous block resolution helper
366
+ async resolvePreviousBlock(previousBlock) {
367
+ if (previousBlock)
368
+ return previousBlock;
369
+ this.log('INFO', 'No previousBlock provided, fetching latest block from chain...');
370
+ if (this.latestContentHash) {
371
+ this.log('INFO', `Using cached latest block's contentHash: ${this.latestContentHash}`);
372
+ return this.latestContentHash;
373
+ }
374
+ const latestHash = await this.chain.getLatestBlockHash();
375
+ if (latestHash && latestHash !== GENESIS_HASH) {
376
+ this.latestContentHash = latestHash;
377
+ this.log('INFO', `Using latest block's contentHash from contract: ${latestHash}`);
378
+ return latestHash;
379
+ }
380
+ this.log('INFO', 'No existing blocks found, using genesis');
381
+ return GENESIS_HASH;
382
+ }
383
+ async handleSubmitBlock(args) {
384
+ const { blockId, previousBlock, contentHash, consensusType, ledger, content, files, encryptionKey } = args;
385
+ // Validate inputs
386
+ validateBlockId(blockId);
387
+ validateBytes32(contentHash, "contentHash");
388
+ if (previousBlock) {
389
+ validateBytes32(previousBlock, "previousBlock");
390
+ }
391
+ // Process files with security validation
392
+ const processedFiles = files && Array.isArray(files)
393
+ ? await Promise.all(files.map(f => this.processSingleFile(f)))
394
+ : files;
395
+ // Resolve previous block hash
396
+ const resolvedPreviousBlock = await this.resolvePreviousBlock(previousBlock);
397
+ // Upload to IPFS if content provided
398
+ let ipfsCID = undefined;
399
+ if (content) {
400
+ ipfsCID = await this.ipfs.upload({
401
+ content,
402
+ files: processedFiles,
403
+ encryptionKey
404
+ });
405
+ }
406
+ // Submit to blockchain
407
+ const tx = await this.chain.submitBlock(blockId, resolvedPreviousBlock, contentHash, consensusType, ledger, ipfsCID);
408
+ const receipt = await tx.wait();
409
+ // Update cache
410
+ this.updateLatestBlockCache(contentHash);
411
+ return this.createSuccessResponse({
412
+ success: true,
413
+ blockId,
414
+ transactionHash: tx.hash,
415
+ blockNumber: receipt.blockNumber,
416
+ gasUsed: receipt.gasUsed.toString(),
417
+ effectiveGasPrice: receipt.effectiveGasPrice?.toString(),
418
+ ipfsCID: ipfsCID || null,
419
+ previousBlock: resolvedPreviousBlock,
420
+ contractAddress: this.chain.getContractAddress(),
421
+ timestamp: new Date().toISOString()
422
+ });
423
+ }
424
+ async handleGetBlock(args) {
425
+ const { blockId } = args;
426
+ const block = await this.chain.getBlock(blockId);
427
+ if (!block) {
428
+ return this.createErrorResponse({
429
+ success: false,
430
+ error: "Block not found on-chain",
431
+ blockId,
432
+ hint: "Verify the blockId or check if the block was submitted"
433
+ });
434
+ }
435
+ return this.createSuccessResponse({
436
+ success: true,
437
+ block: this.formatBlockResponse(block)
438
+ });
439
+ }
440
+ async handleGetLatestBlock() {
441
+ const latestBlock = await this.chain.getLatestBlock();
442
+ if (!latestBlock) {
443
+ return this.createErrorResponse({
444
+ success: false,
445
+ error: "No blocks found on-chain",
446
+ hint: "The chain may be empty. Try submitting a genesis block first."
447
+ });
448
+ }
449
+ return this.createSuccessResponse({
450
+ success: true,
451
+ block: this.formatBlockResponse(latestBlock)
452
+ });
453
+ }
454
+ async handleAuditBlock(args) {
455
+ const { blockId, encryptionKey } = args;
456
+ const block = await this.chain.getBlock(blockId);
457
+ if (!block) {
458
+ return this.createErrorResponse({
459
+ verified: false,
460
+ error: "Block not found on-chain",
461
+ blockId,
462
+ hint: "Cannot audit non-existent block"
463
+ });
464
+ }
465
+ if (!block.ipfsCID) {
466
+ return this.createSuccessResponse({
467
+ verified: null,
468
+ onChainHash: block.contentHash,
469
+ ipfsCID: null,
470
+ note: "V1 block with no IPFS CID - cannot audit actual content",
471
+ blockId,
472
+ agentAddress: block.agentAddress,
473
+ timestamp: block.timestamp
474
+ });
475
+ }
476
+ try {
477
+ const bundle = await this.ipfs.downloadBundle(block.ipfsCID, encryptionKey);
478
+ const computedHash = "0x" + crypto_1.default.createHash("sha256").update(bundle.block).digest("hex");
479
+ const verified = computedHash === block.contentHash;
480
+ return this.createSuccessResponse({
481
+ verified,
482
+ blockId,
483
+ onChainHash: block.contentHash,
484
+ computedHash,
485
+ ipfsCID: block.ipfsCID,
486
+ agentAddress: block.agentAddress,
487
+ timestamp: block.timestamp,
488
+ timestampISO: new Date(block.timestamp * 1000).toISOString(),
489
+ consensusType: block.consensusType,
490
+ ledger: block.ledger,
491
+ contentPreview: bundle.block.substring(0, 500) + (bundle.block.length > 500 ? "..." : ""),
492
+ contentLength: bundle.block.length,
493
+ artifactFiles: Object.keys(bundle.files),
494
+ wasEncrypted: !!encryptionKey
495
+ });
496
+ }
497
+ catch (error) {
498
+ let errorType = "unknown";
499
+ let hint = "";
500
+ if (error.message.includes("encrypted") && !encryptionKey) {
501
+ errorType = "missing_key";
502
+ hint = "This bundle is encrypted. Provide encryptionKey to audit.";
503
+ }
504
+ else if (error.message.includes("Decryption failed") || error.message.includes("decrypt")) {
505
+ errorType = "decryption_failed";
506
+ hint = "The provided encryption key may be incorrect.";
507
+ }
508
+ else if (error.message.includes("not found on-chain")) {
509
+ errorType = "block_not_found";
510
+ hint = "Verify the blockId was submitted correctly.";
511
+ }
512
+ return this.createErrorResponse({
513
+ verified: false,
514
+ error: error.message,
515
+ errorType,
516
+ hint,
517
+ blockId
518
+ });
519
+ }
520
+ }
521
+ async handleVerifyBlock(args) {
522
+ const { blockId, contentHash } = args;
523
+ const contract = this.chain.getContract(false);
524
+ const hashBytes = contentHash.startsWith("0x") ? contentHash : `0x${contentHash}`;
525
+ const isValid = await contract.verifyBlock(blockId, hashBytes);
526
+ return this.createTextResponse(isValid ? "VERIFIED" : "FAILED");
527
+ }
528
+ // Tool handler registry
529
+ toolHandlers = {
530
+ [TOOL_NAMES.SUBMIT_BLOCK]: (args) => this.handleSubmitBlock(args),
531
+ [TOOL_NAMES.GET_BLOCK]: (args) => this.handleGetBlock(args),
532
+ [TOOL_NAMES.GET_LATEST_BLOCK]: () => this.handleGetLatestBlock(),
533
+ [TOOL_NAMES.AUDIT_BLOCK]: (args) => this.handleAuditBlock(args),
534
+ [TOOL_NAMES.VERIFY_BLOCK]: (args) => this.handleVerifyBlock(args),
535
+ };
278
536
  setupHandlers() {
279
537
  this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
280
538
  tools: [SUBMIT_BLOCK_TOOL, GET_BLOCK_TOOL, GET_LATEST_BLOCK_TOOL, AUDIT_BLOCK_TOOL, VERIFY_BLOCK_TOOL],
@@ -282,261 +540,26 @@ class YamoMcpServer {
282
540
  this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
283
541
  const { name, arguments: args } = request.params;
284
542
  try {
285
- if (name === "yamo_submit_block") {
286
- const { blockId, previousBlock, contentHash, consensusType, ledger, content, files, encryptionKey } = args;
287
- // Process files - auto-read if they're file paths (with security fix)
288
- let processedFiles = files;
289
- if (files && Array.isArray(files)) {
290
- processedFiles = await Promise.all(files.map(async (file) => {
291
- // Check if content is a file path that exists
292
- if (typeof file.content === 'string' && fs_1.default.existsSync(file.content)) {
293
- // Security: Resolve symlinks and restrict to cwd (prevents symlink attacks and TOCTOU)
294
- const filePath = fs_1.default.realpathSync(file.content);
295
- const allowedDir = fs_1.default.realpathSync(process.cwd());
296
- // Check for symlinks
297
- const stats = fs_1.default.lstatSync(file.content);
298
- if (stats.isSymbolicLink()) {
299
- throw new Error(`Symbolic links are not allowed: ${file.content}`);
300
- }
301
- if (!filePath.startsWith(allowedDir)) {
302
- throw new Error(`File path outside allowed directory: ${file.content}`);
303
- }
304
- console.error(`[DEBUG] Auto-reading file from path: ${file.content}`);
305
- return {
306
- name: file.name,
307
- content: await fs_1.default.promises.readFile(filePath, 'utf8')
308
- };
309
- }
310
- // Otherwise use content as-is
311
- return file;
312
- }));
313
- }
314
- // Input validation (Part 3: Security Fixes)
315
- validateBytes32(contentHash, "contentHash");
316
- // Auto-fetch previousBlock if not provided
317
- let resolvedPreviousBlock = previousBlock;
318
- if (!resolvedPreviousBlock) {
319
- console.error(`[INFO] No previousBlock provided, fetching latest block from chain...`);
320
- // First, try the cache (most reliable for chain continuation)
321
- if (this.latestContentHash) {
322
- resolvedPreviousBlock = this.latestContentHash;
323
- console.error(`[INFO] Using cached latest block's contentHash: ${resolvedPreviousBlock}`);
324
- }
325
- else {
326
- // Fallback to direct contract state read (reliable)
327
- const latestHash = await this.chain.getLatestBlockHash();
328
- if (latestHash && latestHash !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
329
- resolvedPreviousBlock = latestHash;
330
- this.latestContentHash = latestHash; // Update cache
331
- console.error(`[INFO] Using latest block's contentHash from contract: ${resolvedPreviousBlock}`);
332
- }
333
- else {
334
- // No blocks exist yet, use genesis
335
- resolvedPreviousBlock = "0x0000000000000000000000000000000000000000000000000000000000000000";
336
- console.error(`[INFO] No existing blocks found, using genesis`);
337
- }
338
- }
339
- }
340
- else if (previousBlock) {
341
- validateBytes32(previousBlock, "previousBlock");
342
- }
343
- let ipfsCID = undefined;
344
- if (content) {
345
- ipfsCID = await this.ipfs.upload({
346
- content,
347
- files: processedFiles,
348
- encryptionKey
349
- });
350
- }
351
- const tx = await this.chain.submitBlock(blockId, resolvedPreviousBlock, contentHash, consensusType, ledger, ipfsCID);
352
- const receipt = await tx.wait();
353
- // Update cache with the new block's contentHash for chain continuation
354
- this.latestContentHash = contentHash;
355
- console.error(`[INFO] Updated latestContentHash cache: ${contentHash}`);
356
- return {
357
- content: [{ type: "text", text: JSON.stringify({
358
- success: true,
359
- blockId,
360
- transactionHash: tx.hash,
361
- blockNumber: receipt.blockNumber,
362
- gasUsed: receipt.gasUsed.toString(),
363
- effectiveGasPrice: receipt.effectiveGasPrice?.toString(),
364
- ipfsCID: ipfsCID || null,
365
- previousBlock: resolvedPreviousBlock,
366
- contractAddress: this.chain.getContractAddress(),
367
- timestamp: new Date().toISOString()
368
- }, null, 2) }],
369
- };
370
- }
371
- if (name === "yamo_get_block") {
372
- const { blockId } = args;
373
- const block = await this.chain.getBlock(blockId);
374
- if (!block) {
375
- return {
376
- content: [{ type: "text", text: JSON.stringify({
377
- success: false,
378
- error: "Block not found on-chain",
379
- blockId,
380
- hint: "Verify the blockId or check if the block was submitted"
381
- }, null, 2) }],
382
- isError: true,
383
- };
384
- }
385
- return {
386
- content: [{ type: "text", text: JSON.stringify({
387
- success: true,
388
- block: {
389
- blockId: block.blockId,
390
- previousBlock: block.previousBlock,
391
- agentAddress: block.agentAddress,
392
- contentHash: block.contentHash,
393
- timestamp: block.timestamp,
394
- timestampISO: new Date(block.timestamp * 1000).toISOString(),
395
- consensusType: block.consensusType,
396
- ledger: block.ledger,
397
- ipfsCID: block.ipfsCID || null
398
- }
399
- }, null, 2) }],
400
- };
401
- }
402
- if (name === "yamo_get_latest_block") {
403
- const latestBlock = await this.chain.getLatestBlock();
404
- if (!latestBlock) {
405
- return {
406
- content: [{ type: "text", text: JSON.stringify({
407
- success: false,
408
- error: "No blocks found on-chain",
409
- hint: "The chain may be empty. Try submitting a genesis block first."
410
- }, null, 2) }],
411
- isError: true,
412
- };
413
- }
414
- return {
415
- content: [{ type: "text", text: JSON.stringify({
416
- success: true,
417
- block: {
418
- blockId: latestBlock.blockId,
419
- previousBlock: latestBlock.previousBlock,
420
- agentAddress: latestBlock.agentAddress,
421
- contentHash: latestBlock.contentHash,
422
- timestamp: latestBlock.timestamp,
423
- timestampISO: new Date(latestBlock.timestamp * 1000).toISOString(),
424
- consensusType: latestBlock.consensusType,
425
- ledger: latestBlock.ledger,
426
- ipfsCID: latestBlock.ipfsCID || null
427
- }
428
- }, null, 2) }],
429
- };
430
- }
431
- if (name === "yamo_audit_block") {
432
- const { blockId, encryptionKey } = args;
433
- // Get block from chain
434
- const block = await this.chain.getBlock(blockId);
435
- if (!block) {
436
- return {
437
- content: [{ type: "text", text: JSON.stringify({
438
- verified: false,
439
- error: "Block not found on-chain",
440
- blockId,
441
- hint: "Cannot audit non-existent block"
442
- }, null, 2) }],
443
- isError: true,
444
- };
445
- }
446
- // If no IPFS CID, can't audit content
447
- if (!block.ipfsCID) {
448
- return {
449
- content: [{ type: "text", text: JSON.stringify({
450
- verified: null, // Cannot verify without IPFS
451
- onChainHash: block.contentHash,
452
- ipfsCID: null,
453
- note: "V1 block with no IPFS CID - cannot audit actual content",
454
- blockId,
455
- agentAddress: block.agentAddress,
456
- timestamp: block.timestamp
457
- }, null, 2) }],
458
- };
459
- }
460
- // Download and verify from IPFS
461
- try {
462
- const bundle = await this.ipfs.downloadBundle(block.ipfsCID, encryptionKey);
463
- const computedHash = "0x" + crypto_1.default.createHash("sha256").update(bundle.block).digest("hex");
464
- const verified = computedHash === block.contentHash;
465
- return {
466
- content: [{ type: "text", text: JSON.stringify({
467
- verified,
468
- blockId,
469
- onChainHash: block.contentHash,
470
- computedHash,
471
- ipfsCID: block.ipfsCID,
472
- agentAddress: block.agentAddress,
473
- timestamp: block.timestamp,
474
- timestampISO: new Date(block.timestamp * 1000).toISOString(),
475
- consensusType: block.consensusType,
476
- ledger: block.ledger,
477
- contentPreview: bundle.block.substring(0, 500) + (bundle.block.length > 500 ? "..." : ""),
478
- contentLength: bundle.block.length,
479
- artifactFiles: Object.keys(bundle.files),
480
- wasEncrypted: !!encryptionKey
481
- }, null, 2) }],
482
- };
483
- }
484
- catch (error) {
485
- // Enhanced error messages
486
- let errorType = "unknown";
487
- let hint = "";
488
- if (error.message.includes("encrypted") && !encryptionKey) {
489
- errorType = "missing_key";
490
- hint = "This bundle is encrypted. Provide encryptionKey to audit.";
491
- }
492
- else if (error.message.includes("Decryption failed") || error.message.includes("decrypt")) {
493
- errorType = "decryption_failed";
494
- hint = "The provided encryption key may be incorrect.";
495
- }
496
- else if (error.message.includes("not found on-chain")) {
497
- errorType = "block_not_found";
498
- hint = "Verify the blockId was submitted correctly.";
499
- }
500
- return {
501
- content: [{ type: "text", text: JSON.stringify({
502
- verified: false,
503
- error: error.message,
504
- errorType,
505
- hint,
506
- blockId
507
- }, null, 2) }],
508
- isError: true,
509
- };
510
- }
511
- }
512
- if (name === "yamo_verify_block") {
513
- const { blockId, contentHash } = args;
514
- const contract = this.chain.getContract(false);
515
- const hashBytes = contentHash.startsWith("0x") ? contentHash : `0x${contentHash}`;
516
- const isValid = await contract.verifyBlock(blockId, hashBytes);
517
- return {
518
- content: [{ type: "text", text: isValid ? "VERIFIED" : "FAILED" }],
519
- };
543
+ const handler = this.toolHandlers[name];
544
+ if (!handler) {
545
+ throw new Error(`Unknown tool: ${name}`);
520
546
  }
521
- throw new Error(`Unknown tool: ${name}`);
547
+ return await handler(args);
522
548
  }
523
549
  catch (error) {
524
- return {
525
- content: [{ type: "text", text: JSON.stringify({
526
- success: false,
527
- error: error.message,
528
- tool: name,
529
- timestamp: new Date().toISOString()
530
- }, null, 2) }],
531
- isError: true,
532
- };
550
+ return this.createErrorResponse({
551
+ success: false,
552
+ error: error.message,
553
+ tool: name,
554
+ timestamp: new Date().toISOString()
555
+ });
533
556
  }
534
557
  });
535
558
  }
536
559
  async run() {
537
560
  const transport = new stdio_js_1.StdioServerTransport();
538
561
  await this.server.connect(transport);
539
- console.error(`YAMO MCP Server v${pkg.version} running on stdio`);
562
+ this.log('INFO', `YAMO MCP Server v${pkg.version} running on stdio`);
540
563
  }
541
564
  }
542
565
  const server = new YamoMcpServer();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yamo/mcp-server",
3
- "version": "1.3.10",
3
+ "version": "1.3.11",
4
4
  "description": "YAMO Protocol v0.4 - Model Context Protocol server for AI agents",
5
5
  "main": "dist/index.js",
6
6
  "type": "commonjs",